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

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


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

/**
 * 領収書生成
 *
 * 領収書レコードの管理と番号採番を行う。
 * 入金確認済み（completed）の決済に対してのみ発行可能。
 *
 * @since 1.0.0
 */
class ReceiptGenerator {

	use Singleton;

	private const TABLE = 'conf_receipts';

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

	/**
	 * テーブル名
	 */
	private function table(): string {
		global $wpdb;
		return $wpdb->prefix . self::TABLE;
	}

	/**
	 * 領収書を作成する
	 *
	 * @param array $data {
	 *     @type int    $payment_id          決済ID（必須、completed のみ）
	 *     @type string $addressee_name      宛名（必須）
	 *     @type string $addressee_dept      部署（任意）
	 *     @type string $proviso             但書き（デフォルト: 参加費として）
	 *     @type string $tax_registration_no 適格請求書発行事業者登録番号（任意）
	 * }
	 * @return int|\WP_Error 領収書ID
	 */
	public function create( array $data ): int|\WP_Error {
		global $wpdb;

		if ( empty( $data['payment_id'] ) ) {
			return new \WP_Error( 'missing_payment_id', __( 'Payment ID is required.', 'conferencemanager' ) );
		}

		if ( empty( $data['addressee_name'] ) ) {
			return new \WP_Error( 'missing_addressee', __( 'Addressee is required.', 'conferencemanager' ) );
		}

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

		if ( 'completed' !== $payment['status'] ) {
			return new \WP_Error( 'payment_not_completed', __( 'Receipts can only be issued for confirmed payments.', 'conferencemanager' ) );
		}

		$receipt_no = $this->generate_receipt_no();
		$pdf_path   = 'receipts/' . $receipt_no . '.pdf';

		$insert_data = [
			'payment_id'          => absint( $data['payment_id'] ),
			'receipt_no'          => $receipt_no,
			'addressee_name'      => sanitize_text_field( $data['addressee_name'] ),
			'addressee_dept'      => sanitize_text_field( $data['addressee_dept'] ?? '' ),
			'proviso'             => sanitize_text_field( $data['proviso'] ?? '参加費として' ),
			'tax_registration_no' => sanitize_text_field( $data['tax_registration_no'] ?? '' ),
			'pdf_path'            => $pdf_path,
		];

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table query.
		$result = $wpdb->insert( $this->table(), $insert_data );

		if ( false === $result ) {
			return new \WP_Error(
				'db_insert_error',
				__( 'Failed to create receipt.', 'conferencemanager' ),
				$wpdb->last_error
			);
		}

		$receipt_id = (int) $wpdb->insert_id;

		AuditLog::instance()->log( 'create_receipt', 'receipt', $receipt_id, [
			'payment_id' => $data['payment_id'],
			'receipt_no' => $receipt_no,
		] );

		return $receipt_id;
	}

	/**
	 * 宛名・部署を更新し、同一 receipt_no で PDF を上書き再生成する
	 *
	 * 参加者がマイページから領収書の宛名を編集する用途。receipt_no は変えず
	 * 既存 PDF を上書きするため、常に有効な領収書は1枚（二重発行を防止）。
	 *
	 * @param int    $receipt_id     領収書ID
	 * @param string $addressee_name 宛名（必須）
	 * @param string $addressee_dept 会社名・部署（任意）
	 * @return true|\WP_Error
	 */
	public function update_addressee( int $receipt_id, string $addressee_name, string $addressee_dept = '' ): true|\WP_Error {
		global $wpdb;

		$addressee_name = sanitize_text_field( $addressee_name );
		$addressee_dept = sanitize_text_field( $addressee_dept );

		if ( '' === $addressee_name ) {
			return new \WP_Error( 'missing_addressee', __( 'Addressee is required.', 'conferencemanager' ) );
		}

		$receipt = $this->get( $receipt_id );
		if ( null === $receipt ) {
			return new \WP_Error( 'receipt_not_found', __( 'Receipt not found.', 'conferencemanager' ) );
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table; request-local write, no caching needed.
		$result = $wpdb->update(
			$this->table(),
			[
				'addressee_name' => $addressee_name,
				'addressee_dept' => $addressee_dept,
			],
			[ 'id' => $receipt_id ]
		);

		if ( false === $result ) {
			return new \WP_Error( 'db_update_error', __( 'Failed to update the receipt.', 'conferencemanager' ), $wpdb->last_error );
		}

		// ⭐ PDF の作り直しは要らない。帳票は**要求のたびに DB から描画する**ので、
		//    宛名を書き換えた時点で次のダウンロードに反映される。
		//    以前はここでディスク上の PDF を上書き再生成していたが、
		//    「古い宛名の PDF が残る」事故の温床だった。

		AuditLog::instance()->log( 'update_receipt_addressee', 'receipt', $receipt_id, [
			'addressee_name' => $addressee_name,
			'addressee_dept' => $addressee_dept,
		] );

		return true;
	}

	/**
	 * 領収書を取得する
	 *
	 * @param int $id 領収書ID
	 * @return array|null
	 */
	public function get( int $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 {$this->table()} WHERE id = %d", $id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe internal method.
			ARRAY_A
		);

		return $row ?: null;
	}

	/**
	 * 領収書番号で取得する
	 *
	 * @param string $receipt_no 領収書番号
	 * @return array|null
	 */
	public function get_by_no( string $receipt_no ): ?array {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$row = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM {$this->table()} WHERE receipt_no = %s", $receipt_no ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe internal method.
			ARRAY_A
		);

		return $row ?: null;
	}

	/**
	 * 決済IDで領収書を取得する
	 *
	 * @param int $payment_id 決済ID
	 * @return array|null
	 */
	public function get_by_payment( int $payment_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 {$this->table()} WHERE payment_id = %d ORDER BY id DESC LIMIT 1", $payment_id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe internal method.
			ARRAY_A
		);

		return $row ?: null;
	}

	/**
	 * 領収書に必要な全データを取得する
	 *
	 * @param int $receipt_id 領収書ID
	 * @return array|null
	 */
	public function get_receipt_data( int $receipt_id ): ?array {
		$receipt = $this->get( $receipt_id );
		if ( null === $receipt ) {
			return null;
		}

		$payment = PaymentManager::instance()->get( (int) $receipt['payment_id'] );
		if ( null === $payment ) {
			return null;
		}

		$registration = \ConfManager\Registration\RegistrationManager::instance()->get( (int) $payment['registration_id'] );
		$event        = null;
		if ( null !== $registration ) {
			$event = \ConfManager\Events\EventManager::instance()->get( (int) $registration['event_id'] );
		}

		return [
			'receipt'      => $receipt,
			'payment'      => $payment,
			'registration' => $registration,
			'event'        => $event,
		];
	}

	/**
	 * 領収書 PDF をディスクに書かずにバイナリで返す
	 *
	 * ⭐ 管理画面からの発行に使う。**公開 URL を作らない**ので、
	 * uploads 配下に連番のファイル名で置く generate_pdf() とは別経路。
	 *
	 * @param int $receipt_id 領収書ID
	 * @return string|\WP_Error PDF バイナリ
	 */
	public function render_binary( int $receipt_id ): string|\WP_Error {
		$data = $this->get_receipt_data( $receipt_id );
		if ( null === $data ) {
			return new \WP_Error(
				'receipt_data_not_found',
				__( 'Could not build the receipt. The registration or member record was not found.', 'conferencemanager' )
			);
		}

		$renderer = PdfRenderer::instance();
		$html     = $renderer->load_template( 'receipt.php', $data );

		return $renderer->render_to_string( $html, 'receipt.css' );
	}

	/**
	 * ダウンロード用のファイル名
	 *
	 * @param int $receipt_id 領収書ID
	 */
	public function filename( int $receipt_id ): string {
		$row = $this->get( $receipt_id );
		$no  = (string) ( $row['receipt_no'] ?? $receipt_id );

		return $no . '.pdf';
	}

	/**
	 * 領収書番号を生成する
	 *
	 * フォーマット: RCP-{YYYYMM}-{連番5桁}
	 */
	private function generate_receipt_no(): string {
		global $wpdb;

		$prefix = 'RCP-' . gmdate( 'Ym' ) . '-';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$last = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT receipt_no FROM {$this->table()} WHERE receipt_no LIKE %s ORDER BY id DESC LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from safe internal method.
				$prefix . '%'
			)
		);

		$seq = $last ? (int) substr( $last, -5 ) + 1 : 1;

		return $prefix . str_pad( (string) $seq, 5, '0', STR_PAD_LEFT );
	}
}
