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

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


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

/**
 * 決済 REST API
 *
 * @since 1.0.0
 */
class PaymentApi {

	use Singleton;
	use HasHooks;

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

	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 {
		register_rest_route( self::NAMESPACE, '/payments', [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_items' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_view_payments' ),
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/(?P<id>[\d]+)/confirm', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'confirm_payment' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_payments' ),
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/(?P<id>[\d]+)/refund', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'refund_payment' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_edit_payments' ),
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/summary/(?P<event_id>[\d]+)', [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'get_summary' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_view_payments' ),
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/export/(?P<event_id>[\d]+)', [
			[
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => [ $this, 'export_csv' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_export_payments' ),
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/stripe-webhook', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'handle_stripe_webhook' ],
				'permission_callback' => '__return_true',
			],
		] );

		register_rest_route( self::NAMESPACE, '/payments/stripe-test', [
			[
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => [ $this, 'test_stripe_connection' ],
				'permission_callback' => fn() => current_user_can( 'conf_manager_manage_settings' ),
			],
		] );
	}

	public function get_items( \WP_REST_Request $request ): \WP_REST_Response {
		$result = PaymentManager::instance()->search( [
			'event_id'        => $request->get_param( 'event_id' ),
			'registration_id' => $request->get_param( 'registration_id' ),
			'method'          => $request->get_param( 'method' ),
			'status'          => $request->get_param( 'status' ),
			'date_from'       => $request->get_param( 'date_from' ),
			'date_to'         => $request->get_param( 'date_to' ),
			'per_page'        => $request->get_param( 'per_page' ) ?? 20,
			'page'            => $request->get_param( 'page' ) ?? 1,
		] );

		// 各決済に会員名を一括付加（N+1回避）
		global $wpdb;
		$reg_table    = $wpdb->prefix . 'conf_registrations';
		$member_table = $wpdb->prefix . 'conf_members';

		$reg_ids = array_filter( array_map( fn( $i ) => (int) $i['registration_id'], $result['items'] ) );
		$member_map = [];
		$transfer_note_map = [];
		if ( ! empty( $reg_ids ) ) {
			$placeholders = implode( ',', array_fill( 0, count( $reg_ids ), '%d' ) );
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names from plugin-defined prefix; IN() placeholders built from %d count and bound via spread.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT r.id AS registration_id, r.transfer_note, m.name_sei, m.name_mei FROM {$member_table} m
					 INNER JOIN {$reg_table} r ON r.member_id = m.id
					 WHERE r.id IN ({$placeholders})",
					...$reg_ids
				),
				ARRAY_A
			);
			// phpcs:enable
			foreach ( $rows as $row ) {
				$member_map[ (int) $row['registration_id'] ]        = $row['name_sei'] . ' ' . $row['name_mei'];
				$transfer_note_map[ (int) $row['registration_id'] ] = $row['transfer_note'] ?? '';
			}
		}
		foreach ( $result['items'] as &$item ) {
			$item['member_name']   = $member_map[ (int) $item['registration_id'] ] ?? '';
			$item['transfer_note'] = $transfer_note_map[ (int) $item['registration_id'] ] ?? '';
		}
		unset( $item );

		$response = new \WP_REST_Response( $result['items'], 200 );
		$response->header( 'X-WP-Total', (string) $result['total'] );

		return $response;
	}

	public function confirm_payment( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$id   = (int) $request['id'];
		$name = sanitize_text_field( $request->get_param( 'bank_transfer_name' ) ?? '' );
		$date = sanitize_text_field( $request->get_param( 'bank_transfer_date' ) ?? '' );

		$result = PaymentManager::instance()->confirm_payment( $id, $name, $date );

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

		return new \WP_REST_Response( PaymentManager::instance()->get( $id ), 200 );
	}

	/**
	 * 返金処理
	 */
	public function refund_payment( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
		$id     = (int) $request['id'];
		$amount = absint( $request->get_param( 'amount' ) ?? 0 );
		$reason = sanitize_text_field( $request->get_param( 'reason' ) ?? '' );

		$payment = PaymentManager::instance()->get( $id );
		if ( null === $payment ) {
			return new \WP_Error( 'not_found', __( 'Payment record not found.', 'conferencemanager' ), [ 'status' => 404 ] );
		}

		// Stripe決済の場合はStripe APIを通じて返金
		if ( 'stripe' === $payment['method'] ) {
			$result = StripePayment::instance()->refund( $id, $amount, $reason );
		} else {
			$result = PaymentManager::instance()->refund( $id, $amount, $reason );
		}

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

		return new \WP_REST_Response( [
			'success' => true,
			'payment' => PaymentManager::instance()->get( $id ),
			'message' => 'stripe' === $payment['method']
				? __( 'Refund processed via Stripe.', 'conferencemanager' )
				: __( 'Refund record saved.', 'conferencemanager' ),
		], 200 );
	}

	public function get_summary( \WP_REST_Request $request ): \WP_REST_Response {
		$event_id = (int) $request['event_id'];

		return new \WP_REST_Response( [
			'total'     => PaymentManager::instance()->get_event_summary( $event_id ),
			'by_method' => PaymentManager::instance()->get_summary_by_method( $event_id ),
		], 200 );
	}

	/**
	 * CSV出力（直接出力方式）
	 */
	public function export_csv( \WP_REST_Request $request ): void {
		$event_id = (int) $request['event_id'];
		$data     = PaymentManager::instance()->get_export_data( $event_id );

		$filename = 'payments-event-' . $event_id . '-' . gmdate( 'Ymd' ) . '.csv';

		header( 'Content-Type: text/csv; charset=UTF-8' );
		header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
		header( 'Cache-Control: no-cache, no-store, must-revalidate' );

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Writing to php://output stream for CSV download; WP_Filesystem does not support stream wrappers.
		$output = fopen( 'php://output', 'w' );
		// BOMでExcel UTF-8対応
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Writing to php://output stream (matches fopen above).
		fwrite( $output, "\xEF\xBB\xBF" );

		// 日本語ヘッダー
		$header_labels = [
			'registration_no'    => '登録番号',
			'name_sei'           => '姓',
			'name_mei'           => '名',
			'member_type'        => '会員種別',
			'participation_type' => '参加形態',
			'method'             => '決済手段',
			'amount'             => '金額（税抜）',
			'tax'                => '消費税',
			'fee'                => '手数料',
			'payment_status'     => '決済ステータス',
			'bank_transfer_name' => '振込名義',
			'paid_at'            => '入金日時',
		];
		fputcsv( $output, array_map( fn( $key ) => $header_labels[ $key ] ?? $key, $data['header'] ) );

		// ステータス・決済手段を日本語に変換
		$status_labels = [
			'pending'        => '未入金',
			'completed'      => '入金済',
			'refunded'       => '返金済',
			'partial_refund' => '一部返金',
			'failed'         => '失敗',
		];
		$method_labels = [
			'bank'   => '銀行振込',
			'stripe' => 'Stripe',
		];
		$type_labels = [
			'regular'   => '正会員',
			'student'   => '学生会員',
			'associate' => '賛助会員',
			'honorary'  => '名誉会員',
			'general'   => '非会員',
		];
		$participation_labels = [
			'onsite' => '会場参加',
			'online' => 'オンライン',
		];

		foreach ( $data['rows'] as $row ) {
			$row['payment_status']     = $status_labels[ $row['payment_status'] ] ?? $row['payment_status'];
			$row['method']             = $method_labels[ $row['method'] ] ?? $row['method'];
			$row['member_type']        = $type_labels[ $row['member_type'] ] ?? $row['member_type'];
			$row['participation_type'] = $participation_labels[ $row['participation_type'] ] ?? $row['participation_type'];
			fputcsv( $output, array_map( [ $this, 'sanitize_csv_value' ], $row ) );
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes php://output stream (matches fopen above).
		fclose( $output );
		exit;
	}

	/**
	 * CSVセル値のインジェクション対策
	 *
	 * =, +, -, @ で始まる文字列はExcelで数式として解釈されるため、
	 * シングルクォートをプレフィックスして無害化する。
	 *
	 * @param string|int|float|null $value セル値
	 * @return string|int|float|null サニタイズ済みの値
	 */
	private function sanitize_csv_value( string|int|float|null $value ): string|int|float|null {
		if ( ! is_string( $value ) || '' === $value ) {
			return $value;
		}

		$first = $value[0];
		if ( in_array( $first, [ '=', '+', '-', '@', "\t", "\r", "\n" ], true ) ) {
			return "'" . $value;
		}

		return $value;
	}

	/**
	 * Stripe Webhookハンドラ
	 */
	public function handle_stripe_webhook( \WP_REST_Request $request ): \WP_REST_Response {
		$payload   = $request->get_body();
		$signature = $request->get_header( 'stripe_signature' ) ?? '';

		$result = StripePayment::instance()->handle_webhook( $payload, $signature );

		if ( is_wp_error( $result ) ) {
			return new \WP_REST_Response( [ 'error' => $result->get_error_message() ], 400 );
		}

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

	/**
	 * Stripe接続テスト
	 */
	public function test_stripe_connection(): \WP_REST_Response {
		if ( ! StripePayment::instance()->is_configured() ) {
			return new \WP_REST_Response( [
				'success' => false,
				'message' => __( 'Stripe API key is not configured.', 'conferencemanager' ),
			], 200 );
		}

		try {
			\Stripe\Stripe::setApiKey(
				defined( 'CONF_MANAGER_STRIPE_SECRET_KEY' )
					? CONF_MANAGER_STRIPE_SECRET_KEY
					: (string) get_option( 'conf_manager_stripe_secret_key', '' )
			);
			$account = \Stripe\Account::retrieve();

			return new \WP_REST_Response( [
				'success'  => true,
				'livemode' => ! ( $account->livemode ?? false ) ? false : true,
				'name'     => $account->settings->dashboard->display_name ?? $account->business_profile->name ?? '',
				'message'  => __( 'Successfully connected to Stripe.', 'conferencemanager' ),
			], 200 );
		} catch ( \Stripe\Exception\AuthenticationException $e ) {
			return new \WP_REST_Response( [
				'success' => false,
				'message' => __( 'Invalid API key. Please set the correct key.', 'conferencemanager' ),
			], 200 );
		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			return new \WP_REST_Response( [
				'success' => false,
				'message' => $e->getMessage(),
			], 200 );
		}
	}
}
