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

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


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

/**
 * 請求書生成
 *
 * 請求書レコードの管理と番号採番を行う。
 * PDF生成は別途PDFライブラリ（TCPDF等）が必要。
 * このクラスではレコード管理とHTMLテンプレート出力を担当。
 *
 * @since 1.0.0
 */
class InvoiceGenerator {

	use Singleton;

	private const TABLE = 'conf_invoices';

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

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

	/**
	 * 請求書を作成する
	 *
	 * @param array $data {
	 *     @type int    $payment_id          決済ID（必須）
	 *     @type string $addressee_name      宛名（必須）
	 *     @type string $addressee_dept      部署（任意）
	 *     @type string $honorific           敬称 様|御中（デフォルト: 御中）
	 *     @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' ) );
		}

		$invoice_no = $this->generate_invoice_no();

		// PDF パスは後で設定（generate_pdf() で更新）
		$pdf_path = 'invoices/' . $invoice_no . '.pdf';

		$insert_data = [
			'payment_id'          => absint( $data['payment_id'] ),
			'invoice_no'          => $invoice_no,
			'addressee_name'      => sanitize_text_field( $data['addressee_name'] ),
			'addressee_dept'      => sanitize_text_field( $data['addressee_dept'] ?? '' ),
			'honorific'           => sanitize_text_field( $data['honorific'] ?? '御中' ),
			'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 invoice.', 'conferencemanager' ),
				$wpdb->last_error
			);
		}

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

		AuditLog::instance()->log( 'create_invoice', 'invoice', $invoice_id, [
			'payment_id' => $data['payment_id'],
			'invoice_no' => $invoice_no,
		] );

		return $invoice_id;
	}

	/**
	 * 請求書を取得する
	 *
	 * @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 $invoice_no 請求書番号
	 * @return array|null
	 */
	public function get_by_no( string $invoice_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 invoice_no = %s", $invoice_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;
	}

	/**
	 * 請求書のHTMLデータを取得する（PDF生成前のプレビュー用）
	 *
	 * @param int $invoice_id 請求書ID
	 * @return array|null 請求書に必要な全データ
	 */
	public function get_invoice_data( int $invoice_id ): ?array {
		$invoice = $this->get( $invoice_id );
		if ( null === $invoice ) {
			return null;
		}

		$payment = PaymentManager::instance()->get( (int) $invoice['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 [
			'invoice'      => $invoice,
			'payment'      => $payment,
			'registration' => $registration,
			'event'        => $event,
		];
	}

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

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

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

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

		return $no . '.pdf';
	}

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

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

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table query.
		$last = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT invoice_no FROM {$this->table()} WHERE invoice_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 );
	}
}
