<?php
declare(strict_types=1);
namespace ConfManager\Members;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}


use ConfManager\Traits\Singleton;
use ConfManager\Traits\HasHooks;

/**
 * 会員REST APIエンドポイント
 *
 * /wp-json/conf-manager/v1/members で会員CRUDを提供する。
 * 管理者権限（manage_options）を持つユーザーのみアクセス可能。
 *
 * @since 1.0.0
 */
class MemberApi {

	use Singleton;
	use HasHooks;

	private const NAMESPACE = 'conf-manager/v1';
	private const REST_BASE = 'members';

	/**
	 * 初期化
	 */
	protected function init(): void {
		$this->register_hooks();
	}

	/**
	 * フック登録
	 */
	protected function register_hooks(): void {
		$this->add_action( 'rest_api_init', [ $this, 'register_routes' ] );
	}

	/**
	 * ルート登録
	 */
	public function register_routes(): void {
		// GET /members — 一覧・検索
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE, [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_view_members' ),
				'args'                => $this->get_collection_params(),
			],
			// POST /members — 新規作成
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'create_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_members' ),
				'args'                => $this->get_create_params(),
			],
		] );

		// GET/PUT/DELETE /members/{id}
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE . '/(?P<id>[\d]+)', [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_view_members' ),
				'args'                => [
					'id' => [
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					],
				],
			],
			[
				'methods'             => \WP_REST_Server::EDITABLE,
				'callback'            => [ $this, 'update_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_members' ),
				'args'                => $this->get_update_params(),
			],
			[
				'methods'             => \WP_REST_Server::DELETABLE,
				'callback'            => [ $this, 'delete_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_members' ),
				'args'                => [
					'id' => [
						'type'              => 'integer',
						'required'          => true,
						'sanitize_callback' => 'absint',
					],
				],
			],
		] );

		// POST /members/import — CSVインポート
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE . '/import', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'import_items' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_import_members' ),
			],
		] );

		// GET /members/export — CSVエクスポート
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE . '/export', [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'export_items' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_export_members' ),
				'args'                => $this->get_collection_params(),
			],
		] );

		// POST /members/{id}/approve — 承認
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE . '/(?P<id>[\d]+)/approve', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'approve_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_members' ),
			],
		] );

		// POST /members/{id}/reject — 却下
		register_rest_route( self::NAMESPACE, '/' . self::REST_BASE . '/(?P<id>[\d]+)/reject', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'reject_item' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_members' ),
			],
		] );
	}

	/**
	 * POST /members/{id}/approve — 承認
	 */
	public function approve_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$result = MemberManager::instance()->approve( (int) $request['id'] );
		if ( is_wp_error( $result ) ) {
			$result->add_data( [ 'status' => 400 ] );
			return $result;
		}
		return new \WP_REST_Response( [ 'success' => true ], 200 );
	}

	/**
	 * POST /members/{id}/reject — 却下
	 */
	public function reject_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$result = MemberManager::instance()->reject( (int) $request['id'] );
		if ( is_wp_error( $result ) ) {
			$result->add_data( [ 'status' => 400 ] );
			return $result;
		}
		return new \WP_REST_Response( [ 'success' => true ], 200 );
	}

	/**
	 * GET /members — 一覧取得
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response
	 */
	public function get_items( \WP_REST_Request $request ): \WP_REST_Response {
		$manager = MemberManager::instance();

		$search_args = [
			'keyword'     => $request->get_param( 'keyword' ),
			'member_type' => $request->get_param( 'member_type' ),
			'status'      => $request->get_param( 'status' ) ?? 'active',
			'member_no'   => $request->get_param( 'member_no' ),
			'orderby'     => $request->get_param( 'orderby' ) ?? 'id',
			'order'       => $request->get_param( 'order' ) ?? 'DESC',
			'per_page'    => $request->get_param( 'per_page' ) ?? 20,
			'page'        => $request->get_param( 'page' ) ?? 1,
		];
		if ( null !== $request->get_param( 'is_member' ) ) {
			$search_args['is_member'] = $request->get_param( 'is_member' );
		}
		if ( null !== $request->get_param( 'approval_status' ) ) {
			$search_args['approval_status'] = $request->get_param( 'approval_status' );
		}
		$result = $manager->search( $search_args );

		$items = array_map( [ $this, 'prepare_item_for_response' ], $result['items'] );

		$response = new \WP_REST_Response( $items, 200 );
		$response->header( 'X-WP-Total', (string) $result['total'] );
		$response->header( 'X-WP-TotalPages', (string) max( 1, (int) ceil( $result['total'] / $result['per_page'] ) ) );

		return $response;
	}

	/**
	 * GET /members/{id} — 単一取得
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function get_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$manager = MemberManager::instance();
		$member  = $manager->get( (int) $request['id'] );

		if ( null === $member ) {
			return new \WP_Error( 'not_found', __( 'Member not found.', 'conferencemanager' ), [ 'status' => 404 ] );
		}

		return new \WP_REST_Response( $this->prepare_item_for_response( $member ), 200 );
	}

	/**
	 * POST /members — 新規作成
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function create_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$manager = MemberManager::instance();

		$data = [
			'member_no'     => $request->get_param( 'member_no' ),
			'member_type'   => $request->get_param( 'member_type' ) ?? 'regular',
			'name_sei'      => $request->get_param( 'name_sei' ),
			'name_mei'      => $request->get_param( 'name_mei' ),
			'name_sei_kana' => $request->get_param( 'name_sei_kana' ),
			'name_mei_kana' => $request->get_param( 'name_mei_kana' ),
			'email'         => $request->get_param( 'email' ),
			'phone'         => $request->get_param( 'phone' ),
			'org_name'      => $request->get_param( 'org_name' ),
			'org_dept'      => $request->get_param( 'org_dept' ),
			'org_address'   => $request->get_param( 'org_address' ),
			'wp_user_id'    => $request->get_param( 'wp_user_id' ),
		];

		// 🔴 人がフォームに打ち込む入口だけ電話番号を必須にする。
		//    CSV インポート（MemberImport → create()）と既存会員の編集（update_item）は
		//    対象外。判定を MemberManager::validate() に移すと、その除外が消える。
		$phone_check = MemberManager::require_phone( $data );
		if ( is_wp_error( $phone_check ) ) {
			$phone_check->add_data( [ 'status' => 400 ] );
			return $phone_check;
		}

		$result = $manager->create( $data );

		if ( is_wp_error( $result ) ) {
			$result->add_data( [ 'status' => 400 ] );
			return $result;
		}

		$member = $manager->get( $result );

		return new \WP_REST_Response( $this->prepare_item_for_response( $member ), 201 );
	}

	/**
	 * PUT /members/{id} — 更新
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function update_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$manager = MemberManager::instance();
		$id      = (int) $request['id'];

		$data = array_filter( [
			'member_no'     => $request->get_param( 'member_no' ),
			'member_type'   => $request->get_param( 'member_type' ),
			'name_sei'      => $request->get_param( 'name_sei' ),
			'name_mei'      => $request->get_param( 'name_mei' ),
			'name_sei_kana' => $request->get_param( 'name_sei_kana' ),
			'name_mei_kana' => $request->get_param( 'name_mei_kana' ),
			'email'         => $request->get_param( 'email' ),
			'phone'         => $request->get_param( 'phone' ),
			'org_name'      => $request->get_param( 'org_name' ),
			'org_dept'      => $request->get_param( 'org_dept' ),
			'org_address'   => $request->get_param( 'org_address' ),
			'wp_user_id'    => $request->get_param( 'wp_user_id' ),
		], fn( $v ) => null !== $v );

		$result = $manager->update( $id, $data );

		if ( is_wp_error( $result ) ) {
			$status = 'not_found' === $result->get_error_code() ? 404 : 400;
			$result->add_data( [ 'status' => $status ] );
			return $result;
		}

		$member = $manager->get( $id );

		return new \WP_REST_Response( $this->prepare_item_for_response( $member ), 200 );
	}

	/**
	 * DELETE /members/{id} — 論理削除
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function delete_item( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$manager = MemberManager::instance();
		$result  = $manager->delete( (int) $request['id'] );

		if ( is_wp_error( $result ) ) {
			$result->add_data( [ 'status' => 404 ] );
			return $result;
		}

		return new \WP_REST_Response( [ 'deleted' => true ], 200 );
	}

	/**
	 * POST /members/import — CSVインポート
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function import_items( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$files = $request->get_file_params();

		if ( empty( $files['file']['tmp_name'] ) ) {
			return new \WP_Error(
				'no_file',
				__( 'No CSV file has been uploaded.', 'conferencemanager' ),
				[ 'status' => 400 ]
			);
		}

		$importer = MemberImport::instance();
		$result   = $importer->import_csv( $files['file']['tmp_name'], [
			'update_existing' => (bool) $request->get_param( 'update_existing' ),
			'encoding'        => $request->get_param( 'encoding' ) ?? 'UTF-8',
		] );

		return new \WP_REST_Response( $result, 200 );
	}

	/**
	 * GET /members/export — CSVエクスポート
	 *
	 * @param \WP_REST_Request $request リクエスト
	 * @return \WP_REST_Response
	 */
	public function export_items( \WP_REST_Request $request ): \WP_REST_Response {
		$manager = MemberManager::instance();

		// search() の per_page は 100 に丸められるので、ここに 10000 を渡しても
		// **黙って 100 件で切れる**（成功に見えるのに中身だけ欠ける）。
		// 一括で欲しいものは専用の経路を通す。
		/**
		 * エクスポートで取る行数の上限
		 *
		 * 既定は MemberManager::BULK_MAX。1 リクエストでメモリを食い潰さない
		 * ための安全弁で、当たったことは戻り値と CSV の最終行の両方で分かる。
		 *
		 * @param int $max 上限行数
		 */
		$max = (int) apply_filters( 'conf_manager_export_max_rows', MemberManager::BULK_MAX );

		$result = $manager->search_all(
			[
				'keyword'     => $request->get_param( 'keyword' ),
				'member_type' => $request->get_param( 'member_type' ),
				'status'      => $request->get_param( 'status' ) ?? 'active',
			],
			max( 1, $max )
		);

		$header = MemberImport::get_template_header();
		$rows   = [];

		foreach ( $result['items'] as $member ) {
			$row = [];
			foreach ( $header as $col ) {
				$row[] = $member[ $col ] ?? '';
			}
			$rows[] = $row;
		}

		// 上限で打ち切ったことを**ファイルの中身にも残す**。戻り値の truncated は
		// 呼び出し側が捨てられるが、行として出ていれば開いた人が気づく。
		if ( ! empty( $result['truncated'] ) ) {
			$notice    = array_fill( 0, max( 1, count( $header ) ), '' );
			$notice[0] = sprintf(
				/* translators: %d: number of rows written to the export */
				__( 'Note: this export stopped at %d rows. Narrow the search and export again to get the rest.', 'conferencemanager' ),
				count( $rows )
			);
			$rows[] = $notice;
		}

		return new \WP_REST_Response( [
			'header'    => $header,
			'rows'      => $rows,
			'total'     => $result['total'],
			'truncated' => ! empty( $result['truncated'] ),
		], 200 );
	}

	/**
	 * レスポンス用にデータを整形する
	 *
	 * @param array $member 会員データ（復号済み）
	 * @return array
	 */
	private function prepare_item_for_response( array $member ): array {
		return [
			'id'            => (int) $member['id'],
			'wp_user_id'    => $member['wp_user_id'] ? (int) $member['wp_user_id'] : null,
			'member_no'     => $member['member_no'] ?? '',
			'member_type'   => $member['member_type'],
			'name_sei'      => $member['name_sei'],
			'name_mei'      => $member['name_mei'],
			'name_sei_kana' => $member['name_sei_kana'] ?? '',
			'name_mei_kana' => $member['name_mei_kana'] ?? '',
			'email'         => $member['email'],
			'phone'         => $member['phone'] ?? '',
			'org_name'      => $member['org_name'] ?? '',
			'org_dept'      => $member['org_dept'] ?? '',
			'org_address'   => $member['org_address'] ?? '',
			'is_member'       => (int) ( $member['is_member'] ?? 1 ),
			'approval_status' => $member['approval_status'] ?? 'auto_approved',
			'registered_via'  => $member['registered_via'] ?? 'admin',
			'status'          => $member['status'],
			'created_at'    => $member['created_at'],
			'updated_at'    => $member['updated_at'],
		];
	}

	/**
	 * 一覧取得パラメータ定義
	 *
	 * @return array
	 */
	private function get_collection_params(): array {
		return [
			'keyword'     => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'member_type' => [
				'type'              => 'string',
				'enum'              => MemberManager::get_valid_types(),
				'sanitize_callback' => 'sanitize_text_field',
			],
			'member_no'   => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'status'      => [
				'type'              => 'string',
				'default'           => 'active',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'orderby'     => [
				'type'    => 'string',
				'default' => 'id',
				'enum'    => [ 'id', 'member_no', 'name_sei', 'member_type', 'org_name', 'created_at' ],
			],
			'order'       => [
				'type'    => 'string',
				'default' => 'DESC',
				'enum'    => [ 'ASC', 'DESC' ],
			],
			'per_page'    => [
				'type'              => 'integer',
				'default'           => 20,
				'minimum'           => 1,
				'maximum'           => 100,
				'sanitize_callback' => 'absint',
			],
			'page'        => [
				'type'              => 'integer',
				'default'           => 1,
				'minimum'           => 1,
				'sanitize_callback' => 'absint',
			],
		];
	}

	/**
	 * 作成パラメータ定義
	 *
	 * @return array
	 */
	private function get_create_params(): array {
		return [
			'name_sei'      => [
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_text_field',
			],
			'name_mei'      => [
				'type'              => 'string',
				'required'          => true,
				'sanitize_callback' => 'sanitize_text_field',
			],
			'email'         => [
				'type'              => 'string',
				'required'          => true,
				'format'            => 'email',
				'sanitize_callback' => 'sanitize_email',
			],
			'member_no'     => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'member_type'   => [
				'type'    => 'string',
				'default' => 'regular',
				'enum'    => MemberManager::get_valid_types(),
			],
			'name_sei_kana' => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'name_mei_kana' => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'phone'         => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'org_name'      => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'org_dept'      => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'org_address'   => [
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
			],
			'wp_user_id'    => [
				'type'              => 'integer',
				'sanitize_callback' => 'absint',
			],
		];
	}

	/**
	 * 更新パラメータ定義
	 *
	 * @return array
	 */
	private function get_update_params(): array {
		$params = $this->get_create_params();
		// 更新時は全て任意
		foreach ( $params as &$param ) {
			unset( $param['required'] );
		}
		$params['id'] = [
			'type'              => 'integer',
			'required'          => true,
			'sanitize_callback' => 'absint',
		];
		return $params;
	}
}
