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

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


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

/**
 * 銀行振込決済
 *
 * 銀行振込の決済作成、振込通知受付、入金確認（消込）を行う。
 * CSVによる一括入金消込にも対応。
 *
 * @since 1.0.0
 */
class BankTransfer {

	use Singleton;

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

	/**
	 * 銀行振込の決済を作成する
	 *
	 * 申込から金額を自動取得して決済レコードを作成。
	 *
	 * @param int    $registration_id    登録ID
	 * @param string $bank_transfer_name 振込名義（任意）
	 * @return int|\WP_Error 決済ID
	 */
	public function create_payment( int $registration_id, string $bank_transfer_name = '' ): int|\WP_Error {
		$reg = RegistrationManager::instance()->get( $registration_id );
		if ( null === $reg ) {
			return new \WP_Error( 'registration_not_found', __( 'Registration not found.', 'conferencemanager' ) );
		}

		// 既存の pending 決済がある場合はそれを返す
		$existing = $this->get_pending_payment( $registration_id );
		if ( null !== $existing ) {
			return (int) $existing['id'];
		}

		$data = [
			'registration_id'    => $registration_id,
			'method'             => 'bank',
			'amount'             => (int) $reg['amount'],
			'tax'                => (int) $reg['tax'],
			'is_group_payment'   => ! empty( $reg['group_id'] ) && ! empty( $reg['is_representative'] ),
		];

		if ( '' !== $bank_transfer_name ) {
			$data['bank_transfer_name'] = $bank_transfer_name;
		}

		return PaymentManager::instance()->create( $data );
	}

	/**
	 * 振込通知を受け付ける
	 *
	 * 参加者が「振込しました」を報告する際に使用。
	 *
	 * @param int    $payment_id         決済ID
	 * @param string $bank_transfer_name 振込名義
	 * @param string $bank_transfer_date 振込日（Y-m-d）
	 * @return true|\WP_Error
	 */
	public function notify_transfer( int $payment_id, string $bank_transfer_name, string $bank_transfer_date ): true|\WP_Error {
		global $wpdb;

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

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

		if ( 'pending' !== $payment['status'] ) {
			return new \WP_Error( 'invalid_status', __( 'This payment has already been processed.', 'conferencemanager' ) );
		}

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

		if ( false === $result ) {
			return new \WP_Error( 'db_update_error', __( 'Failed to save transfer notification.', 'conferencemanager' ) );
		}

		// 申込ステータスを confirmed に
		RegistrationManager::instance()->update_status( (int) $payment['registration_id'], 'confirmed' );

		AuditLog::instance()->log( 'notify_bank_transfer', 'payment', $payment_id, [
			'bank_transfer_name' => $bank_transfer_name,
			'bank_transfer_date' => $bank_transfer_date,
		] );

		return true;
	}

	/**
	 * CSVで一括入金消込を行う
	 *
	 * CSVフォーマット: registration_no, bank_transfer_name, bank_transfer_date
	 *
	 * @param string $csv_path CSVファイルパス
	 * @return array{confirmed: int, skipped: int, errors: array}
	 */
	public function bulk_confirm_from_csv( string $csv_path ): array {
		$result = [
			'confirmed' => 0,
			'skipped'   => 0,
			'errors'    => [],
		];

		if ( ! file_exists( $csv_path ) || ! is_readable( $csv_path ) ) {
			$result['errors'][] = [
				'row'     => 0,
				'message' => __( 'CSV file not found.', 'conferencemanager' ),
			];
			return $result;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Streaming read of admin-uploaded CSV; WP_Filesystem has no streaming reader.
		$handle = fopen( $csv_path, 'r' );
		if ( false === $handle ) {
			$result['errors'][] = [
				'row'     => 0,
				'message' => __( 'Cannot open CSV file.', 'conferencemanager' ),
			];
			return $result;
		}

		// ヘッダー行スキップ
		$header = fgetcsv( $handle, null, ',', '"', '' );
		if ( false === $header ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Matches fopen() above for streaming CSV read.
			fclose( $handle );
			return $result;
		}

		// BOM除去
		if ( isset( $header[0] ) ) {
			$header[0] = preg_replace( '/^\x{FEFF}/u', '', $header[0] );
		}

		$reg_mgr = RegistrationManager::instance();
		$pay_mgr = PaymentManager::instance();
		$row_num = 1;

		while ( false !== ( $row = fgetcsv( $handle, null, ',', '"', '' ) ) ) {
			$row_num++;

			if ( null === $row || [ null ] === $row ) {
				continue;
			}

			$registration_no    = trim( $row[0] ?? '' );
			$bank_transfer_name = trim( $row[1] ?? '' );
			$bank_transfer_date = trim( $row[2] ?? '' );

			if ( '' === $registration_no ) {
				$result['errors'][] = [
					'row'     => $row_num,
					'message' => __( 'Registration number is empty.', 'conferencemanager' ),
				];
				continue;
			}

			// 登録番号から申込を取得
			$reg = $reg_mgr->get_by_no( $registration_no );
			if ( null === $reg ) {
				$result['errors'][] = [
					'row'     => $row_num,
					/* translators: %s: registration number */
				'message' => sprintf( __( 'Registration with number %s not found.', 'conferencemanager' ), $registration_no ),
				];
				continue;
			}

			// pending の決済を取得
			$payment = $this->get_pending_payment( (int) $reg['id'] );
			if ( null === $payment ) {
				$result['skipped']++;
				continue;
			}

			// 入金確認
			$confirm = $pay_mgr->confirm_payment(
				(int) $payment['id'],
				$bank_transfer_name,
				$bank_transfer_date
			);

			if ( is_wp_error( $confirm ) ) {
				$result['errors'][] = [
					'row'     => $row_num,
					'message' => $confirm->get_error_message(),
				];
			} else {
				$result['confirmed']++;
			}
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Matches fopen() above for streaming CSV read.
		fclose( $handle );

		AuditLog::instance()->log( 'bulk_confirm_payments', 'payment', null, [
			'confirmed' => $result['confirmed'],
			'skipped'   => $result['skipped'],
			'errors'    => count( $result['errors'] ),
		] );

		return $result;
	}

	/**
	 * 未入金の決済一覧を取得する
	 *
	 * @param int $days_overdue 超過日数（デフォルト: 0=全件）
	 * @return array
	 */
	public function get_unpaid_list( int $days_overdue = 0 ): array {
		global $wpdb;

		$table = $wpdb->prefix . 'conf_payments';

		if ( $days_overdue > 0 ) {
			$cutoff = gmdate( 'Y-m-d H:i:s', time() - ( $days_overdue * DAY_IN_SECONDS ) );
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from safe internal variable; values bound via prepare().
			$rows   = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table} WHERE method = %s AND status = %s AND created_at < %s ORDER BY created_at ASC",
					'bank',
					'pending',
					$cutoff
				),
				ARRAY_A
			);
			// phpcs:enable
		} else {
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from safe internal variable; values bound via prepare().
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table} WHERE method = %s AND status = %s ORDER BY created_at ASC",
					'bank',
					'pending'
				),
				ARRAY_A
			);
			// phpcs:enable
		}

		return $rows ?: [];
	}

	/**
	 * 登録IDの pending 決済を取得する
	 *
	 * @param int $registration_id 登録ID
	 * @return array|null
	 */
	public function get_pending_payment( int $registration_id ): ?array {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}conf_payments WHERE registration_id = %d AND method = 'bank' AND status = 'pending' ORDER BY id DESC LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe $wpdb->prefix.
				$registration_id
			),
			ARRAY_A
		);

		return $row ?: null;
	}
}
