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

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


use ConfManager\Traits\Singleton;
use ConfManager\Security\AuditLog;
use ConfManager\Registration\RegistrationManager;

/**
 * Stripe決済
 *
 * Stripe Payment Intentsを使用した決済処理。
 * Stripe Elements によるフロントエンド決済と Webhook による非同期確認。
 * サイト管理者自身の Stripe アカウント（API キー）を使用する。
 *
 * 必要: composer require stripe/stripe-php
 *
 * @since 1.0.0
 */
class StripePayment {

	use Singleton;

	/**
	 * Stripe手数料率（3.6%）
	 */
	private const FEE_RATE = 0.036;

	/**
	 * 初期化
	 */
	protected function init(): void {}

	/**
	 * Stripe APIキーが設定されているか確認する
	 *
	 * @return bool
	 */
	public function is_configured(): bool {
		return '' !== $this->get_secret_key() && '' !== $this->get_publishable_key();
	}

	/**
	 * Payment Intent を作成する
	 *
	 * @param int $registration_id 登録ID
	 * @return array{client_secret: string, payment_id: int}|\WP_Error
	 */
	public function create_payment_intent( int $registration_id ): array|\WP_Error {
		if ( ! $this->is_configured() ) {
			return new \WP_Error( 'not_configured', __( 'Stripe API key is not configured.', 'conferencemanager' ) );
		}

		$reg = RegistrationManager::instance()->get( $registration_id );
		if ( null === $reg ) {
			return new \WP_Error( 'registration_not_found', __( 'Registration not found.', 'conferencemanager' ) );
		}

		$amount = (int) $reg['amount'] + (int) $reg['tax'];
		if ( $amount <= 0 ) {
			return new \WP_Error( 'invalid_amount', __( 'Invalid payment amount.', 'conferencemanager' ) );
		}

		$fee = $this->calculate_fee( $amount );

		// Stripe API コール
		try {
			$this->init_stripe();

			$intent = \Stripe\PaymentIntent::create( [
				'amount'   => $amount, // 日本円はそのまま（小数点なし）
				'currency' => 'jpy',
				'metadata' => [
					'registration_id' => $registration_id,
					'registration_no' => $reg['registration_no'],
				],
			] );
		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			return new \WP_Error( 'stripe_error', $e->getMessage() );
		}

		// 決済レコード作成
		$payment_id = PaymentManager::instance()->create( [
			'registration_id' => $registration_id,
			'method'          => 'stripe',
			'amount'          => (int) $reg['amount'],
			'tax'             => (int) $reg['tax'],
			'fee'             => $fee,
			'gateway_txn_id'  => $intent->id,
		] );

		if ( is_wp_error( $payment_id ) ) {
			return $payment_id;
		}

		AuditLog::instance()->log( 'create_stripe_intent', 'payment', $payment_id, [
			'stripe_intent_id' => $intent->id,
			'amount'           => $amount,
		] );

		return [
			'client_secret' => $intent->client_secret,
			'payment_id'    => $payment_id,
		];
	}

	/**
	 * Webhook イベントを処理する
	 *
	 * @param string $payload   リクエストボディ
	 * @param string $signature Stripe-Signatureヘッダー
	 * @return true|\WP_Error
	 */
	public function handle_webhook( string $payload, string $signature ): true|\WP_Error {
		if ( ! $this->is_configured() ) {
			return new \WP_Error( 'not_configured', __( 'Invalid Stripe configuration.', 'conferencemanager' ) );
		}

		$webhook_secret = $this->get_webhook_secret();
		if ( '' === $webhook_secret ) {
			return new \WP_Error( 'no_webhook_secret', __( 'Webhook Secret is not configured.', 'conferencemanager' ) );
		}

		try {
			$this->init_stripe();
			$event = \Stripe\Webhook::constructEvent( $payload, $signature, $webhook_secret );
		} catch ( \Stripe\Exception\SignatureVerificationException $e ) {
			return new \WP_Error( 'invalid_signature', __( 'Invalid Webhook signature.', 'conferencemanager' ) );
		} catch ( \Exception $e ) {
			return new \WP_Error( 'webhook_error', $e->getMessage() );
		}

		switch ( $event->type ) {
			case 'payment_intent.succeeded':
				return $this->handle_payment_succeeded( $event->data->object );

			case 'payment_intent.payment_failed':
				return $this->handle_payment_failed( $event->data->object );

			default:
				return true; // 未対応イベントは無視
		}
	}

	/**
	 * Stripe返金を実行する
	 *
	 * @param int    $payment_id 決済ID
	 * @param int    $amount     返金額（0=全額）
	 * @param string $reason     返金理由
	 * @return true|\WP_Error
	 */
	public function refund( int $payment_id, int $amount = 0, string $reason = '' ): true|\WP_Error {
		$payment = PaymentManager::instance()->get( $payment_id );
		if ( null === $payment ) {
			return new \WP_Error( 'not_found', __( 'Payment record not found.', 'conferencemanager' ) );
		}

		if ( 'stripe' !== $payment['method'] ) {
			return new \WP_Error( 'invalid_method', __( 'This is not a Stripe payment.', 'conferencemanager' ) );
		}

		if ( empty( $payment['gateway_txn_id'] ) ) {
			return new \WP_Error( 'no_txn_id', __( 'Stripe transaction ID is missing.', 'conferencemanager' ) );
		}

		$refund_amount = ( 0 === $amount ) ? (int) $payment['amount'] + (int) $payment['tax'] : $amount;

		try {
			$this->init_stripe();

			\Stripe\Refund::create( [
				'payment_intent' => $payment['gateway_txn_id'],
				'amount'         => $refund_amount,
			] );
		} catch ( \Stripe\Exception\ApiErrorException $e ) {
			return new \WP_Error( 'stripe_refund_error', $e->getMessage() );
		}

		// PaymentManager の返金処理
		$result = PaymentManager::instance()->refund( $payment_id, $amount, $reason );

		AuditLog::instance()->log( 'stripe_refund', 'payment', $payment_id, [
			'refund_amount' => $refund_amount,
			'reason'        => $reason,
		] );

		return $result;
	}

	/**
	 * Stripe手数料を計算する（3.6%）
	 *
	 * @param int $amount 決済金額（税込）
	 * @return int 手数料（円）
	 */
	public function calculate_fee( int $amount ): int {
		return (int) ceil( $amount * self::FEE_RATE );
	}

	/**
	 * 決済成功の処理
	 */
	private function handle_payment_succeeded( object $intent ): true|\WP_Error {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$payment_row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}conf_payments WHERE gateway_txn_id = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe $wpdb->prefix.
				$intent->id
			),
			ARRAY_A
		);

		if ( null === $payment_row ) {
			return new \WP_Error( 'payment_not_found', 'Payment record not found for intent: ' . $intent->id );
		}

		$result = PaymentManager::instance()->confirm_payment( (int) $payment_row['id'] );

		AuditLog::instance()->log( 'stripe_payment_succeeded', 'payment', (int) $payment_row['id'], [
			'stripe_intent_id' => $intent->id,
		] );

		return is_wp_error( $result ) ? $result : true;
	}

	/**
	 * 決済失敗の処理
	 */
	private function handle_payment_failed( object $intent ): true|\WP_Error {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$payment_row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}conf_payments WHERE gateway_txn_id = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe $wpdb->prefix.
				$intent->id
			),
			ARRAY_A
		);

		if ( null === $payment_row ) {
			return true; // レコードなしは無視
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; request-local write, no caching needed.
		$wpdb->update(
			$wpdb->prefix . 'conf_payments',
			[ 'status' => 'failed' ],
			[ 'id' => $payment_row['id'] ]
		);

		AuditLog::instance()->log( 'stripe_payment_failed', 'payment', (int) $payment_row['id'], [
			'stripe_intent_id' => $intent->id,
			'error'            => $intent->last_payment_error->message ?? 'Unknown',
		] );

		return true;
	}

	/**
	 * Stripe SDKを初期化する
	 */
	private function init_stripe(): void {
		\Stripe\Stripe::setApiKey( $this->get_secret_key() );
	}

	/**
	 * Stripe Secret Key を取得
	 */
	private function get_secret_key(): string {
		return defined( 'CONF_MANAGER_STRIPE_SECRET_KEY' )
			? CONF_MANAGER_STRIPE_SECRET_KEY
			: (string) get_option( 'conf_manager_stripe_secret_key', '' );
	}

	/**
	 * Stripe Publishable Key を取得
	 */
	private function get_publishable_key(): string {
		return defined( 'CONF_MANAGER_STRIPE_PUBLISHABLE_KEY' )
			? CONF_MANAGER_STRIPE_PUBLISHABLE_KEY
			: (string) get_option( 'conf_manager_stripe_publishable_key', '' );
	}

	/**
	 * Stripe Webhook Secret を取得
	 */
	private function get_webhook_secret(): string {
		return defined( 'CONF_MANAGER_STRIPE_WEBHOOK_SECRET' )
			? CONF_MANAGER_STRIPE_WEBHOOK_SECRET
			: (string) get_option( 'conf_manager_stripe_webhook_secret', '' );
	}

	/**
	 * フロントエンド用の公開キーを取得する
	 *
	 * @return string
	 */
	public function get_publishable_key_for_frontend(): string {
		return $this->get_publishable_key();
	}
}
