<?php

declare(strict_types=1);

namespace App\Modules\Finance\Application\Services;

use App\Core\SchemaRuntime;
use App\Shared\Schema\FinanceSchemaSupport;
use App\Shared\Schema\PartySchemaSupport;
use PDO;
use RuntimeException;
use Throwable;

final class CashPaymentCancelService
{
    public function __construct(private PDO $pdo)
    {
    }

    public function ensureSchema(): void
    {
        SchemaRuntime::ensureMany($this->pdo, [
            [
                'key' => 'cash_payments_schema',
                'callback' => function (): void { FinanceSchemaSupport::ensureCashPaymentsSchema($this->pdo); },
                'migration' => 'sql/migrate_v1_22_cash_payments.sql',
            ],
            [
                'key' => 'parties_schema',
                'callback' => function (): void { PartySchemaSupport::ensurePartiesSchema($this->pdo); },
                'migration' => 'sql/migrate_v1_18_parties.sql',
            ],
            [
                'key' => 'party_ledger_schema',
                'callback' => function (): void { PartySchemaSupport::ensurePartyLedgerSchema($this->pdo); },
                'migration' => 'sql/migrate_v1_20_party_ledger.sql',
            ],
            [
                'key' => 'bank_accounts_schema',
                'callback' => function (): void { FinanceSchemaSupport::ensureBankAccountsSchema($this->pdo); },
                'migration' => 'sql/migrate_v1_21_bank_accounts.sql',
            ],
        ]);
    }

    /** @return array<string,mixed> */
    public function load(int $id): array
    {
        if ($id <= 0) {
            throw new RuntimeException('سند غير موجود.');
        }

        $st = $this->pdo->prepare('SELECT * FROM cash_payments WHERE id=? LIMIT 1');
        $st->execute([$id]);
        $row = $st->fetch();
        if (!$row) {
            throw new RuntimeException('سند غير موجود.');
        }

        if (((string) ($row['payment_type'] ?? 'party')) !== 'expense') {
            throw new RuntimeException('إلغاء السند متاح حاليًا لسندات المصروف فقط.');
        }

        if (strtoupper((string) ($row['status'] ?? 'POSTED')) !== 'POSTED') {
            throw new RuntimeException('لا يمكن إلغاء هذا السند.');
        }

        return $row;
    }

    public function cancel(int $id, string $reason, int $userId): void
    {
        $row = $this->load($id);
        $reason = trim($reason) !== '' ? trim($reason) : 'إلغاء سند صرف مصروف';

        try {
            $this->pdo->beginTransaction();
            $dt = date('Y-m-d H:i:s');
            $amount = (float) ($row['amount'] ?? 0);

            if (((string) ($row['account_source_type'] ?? 'cashbox')) === 'cashbox') {
                cashbox_add_txn($this->pdo, (int) $row['cashbox_id'], $dt, 'in', $amount, 'cash_payment_cancel', $id, $reason, $userId);
            } elseif (((string) ($row['account_source_type'] ?? 'cashbox')) === 'bank_account') {
                $stBank = $this->pdo->prepare('INSERT INTO bank_transactions (bank_account_id, trx_date, trx_type, amount, source_type, source_id, ref_no, notes, user_id) VALUES (?,?,?,?,?,?,?,?,?)');
                $stBank->execute([
                    (int) $row['bank_account_id'],
                    $dt,
                    'in',
                    $amount,
                    'cash_payment_cancel',
                    $id,
                    (string) ($row['ref_no'] ?? '') !== '' ? (string) $row['ref_no'] : null,
                    $reason,
                    $userId,
                ]);
            } else {
                throw new RuntimeException('تمت إزالة التسوية على عهدة موظف من هذه النسخة.');
            }

            $up = $this->pdo->prepare("UPDATE cash_payments SET status='CANCELED', canceled_at=?, canceled_by=?, cancel_reason=? WHERE id=? AND status='POSTED'");
            $up->execute([$dt, $userId, $reason, $id]);
            audit_log('CANCEL_CASH_PAYMENT_EXPENSE', 'cash_payments', $id, ['reason' => $reason, 'amount' => $amount]);
            $this->pdo->commit();
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }
}
