<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\SchemaRuntime;
use App\Repositories\CheckRepository;
use App\Shared\Schema\ChecksSchemaSupport;
use App\Shared\Schema\FinanceSchemaSupport;
use App\Shared\Schema\PartySchemaSupport;
use InvalidArgumentException;
use PDO;

final class CheckService
{
    public function __construct(private PDO $pdo, private CheckRepository $repo)
    {
    }

    public function ensureSchemaOrThrow(bool $needsCashboxes = false): void
    {
        SchemaRuntime::ensure($this->pdo, 'checks_schema', function (): void { ChecksSchemaSupport::ensureChecksSchema($this->pdo); }, 'sql/migrate_v1_32_checks.sql');
        SchemaRuntime::ensure($this->pdo, 'parties_schema', function (): void { PartySchemaSupport::ensurePartiesSchema($this->pdo); }, 'sql/migrate_v1_18_parties.sql');
        SchemaRuntime::ensure($this->pdo, 'parties_migration_bridge', function (): void { PartySchemaSupport::ensurePartiesMigrateFromCustomersSuppliers($this->pdo); }, 'sql/migrate_v1_18_parties.sql');
        SchemaRuntime::ensure($this->pdo, 'party_ledger_schema', function (): void { PartySchemaSupport::ensurePartyLedgerSchema($this->pdo); }, 'sql/migrate_v1_20_party_ledger.sql');
        if ($needsCashboxes) {
            SchemaRuntime::ensure($this->pdo, 'cashboxes_schema', function (): void { FinanceSchemaSupport::ensureCashboxesSchema($this->pdo); }, 'sql/migrate_v1_27_cashboxes.sql');
        }
    }

    public function list(string $q, string $status): array
    {
        return [
            'rows' => $this->repo->search($q, $status),
            'q' => $q,
            'status' => $status,
            'page_title' => 'سجل الشيكات',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات'],
                ['label'=>'سجل الشيكات'],
            ],
        ];
    }

    public function loadReceiveForm(): array
    {
        return [
            'form' => [
                'receive_date' => date('Y-m-d\TH:i'),
                'drawer_party_id' => 0,
                'drawer_name' => '',
                'check_no' => '',
                'amount' => '',
                'bank_name' => '',
                'issue_date' => date('Y-m-d'),
                'due_date' => '',
                'notes' => '',
            ],
            'errors' => [],
            'page_title' => 'استلام شيك من عميل',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات'],
                ['label'=>'استلام شيك'],
            ],
        ];
    }

    public function receive(array $input, int $userId): int
    {
        $form = $this->normalizeReceive($input);
        $this->pdo->beginTransaction();
        try {
            $checkId = $this->repo->insertReceived($form, $userId);
            $this->repo->addMovement($checkId, 'receive', $form['movement_dt'], $form['drawer_party_id'], (float)$form['amount'], $form['notes'] !== '' ? $form['notes'] : null, $userId);
            party_add_ledger($this->pdo, (int)$form['drawer_party_id'], 'CHECK_RECEIVE', $checkId, $form['check_no'], substr($form['receive_date'],0,10), 0.0, (float)$form['amount'], 'استلام شيك من العميل');
            audit_log('CHECK_RECEIVE', 'checks', $checkId, ['check_no'=>$form['check_no'], 'amount'=>(float)$form['amount']]);
            $this->pdo->commit();
            return $checkId;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function loadView(int $id): array
    {
        $check = $this->repo->findById($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        return [
            'check' => $check,
            'movements' => $this->repo->movements($id),
            'page_title' => 'عرض شيك',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات','url'=>'/finance/checks'],
                ['label'=>'عرض شيك'],
            ],
        ];
    }

    public function loadEndorseForm(int $id): array
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_endorse_check($check)) throw new InvalidArgumentException('لا يمكن تظهير هذا الشيك في حالته الحالية.');
        return [
            'check' => $check,
            'form' => ['movement_date' => date('Y-m-d\TH:i'), 'supplier_party_id' => 0, 'supplier_name' => '', 'notes' => ''],
            'errors' => [],
            'page_title' => 'تظهير شيك لمورد',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات','url'=>'/finance/checks'],
                ['label'=>'تظهير شيك'],
            ],
        ];
    }

    public function endorse(int $id, array $input, int $userId): void
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_endorse_check($check)) throw new InvalidArgumentException('لا يمكن تظهير هذا الشيك في حالته الحالية.');
        $movementDate = trim((string)($input['movement_date'] ?? date('Y-m-d\TH:i')));
        $supplierPartyId = (int)($input['supplier_party_id'] ?? 0);
        $notes = trim((string)($input['notes'] ?? ''));
        if ($supplierPartyId <= 0) throw new InvalidArgumentException('يجب اختيار المورد.');
        if ($movementDate === '') throw new InvalidArgumentException('تاريخ التظهير مطلوب.');
        $dt = str_replace('T', ' ', $movementDate) . ':00';

        $this->pdo->beginTransaction();
        try {
            $this->repo->endorse($id, $supplierPartyId);
            $this->repo->addMovement($id, 'endorse', $dt, $supplierPartyId, (float)$check['amount'], $notes !== '' ? $notes : null, $userId);
            party_add_ledger($this->pdo, $supplierPartyId, 'CHECK_ENDORSE', $id, (string)$check['check_no'], substr($movementDate,0,10), (float)$check['amount'], 0.0, 'تظهير شيك لمورد');
            audit_log('CHECK_ENDORSE', 'checks', $id, ['supplier_party_id'=>$supplierPartyId]);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function loadReturnForm(int $id): array
    {
        $check = $this->repo->findForReturn($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_return_check($check)) throw new InvalidArgumentException('لا يمكن تنفيذ رجوع لهذا الشيك في حالته الحالية.');
        return [
            'check' => $check,
            'form' => ['movement_date' => date('Y-m-d\TH:i'), 'notes' => ''],
            'errors' => [],
            'page_title' => 'رجوع شيك',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات','url'=>'/finance/checks'],
                ['label'=>'رجوع شيك'],
            ],
        ];
    }

    public function performReturn(int $id, array $input, int $userId): void
    {
        $check = $this->repo->findForReturn($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_return_check($check)) throw new InvalidArgumentException('لا يمكن تنفيذ رجوع لهذا الشيك في حالته الحالية.');
        $movementDate = trim((string)($input['movement_date'] ?? date('Y-m-d\TH:i')));
        $notes = trim((string)($input['notes'] ?? ''));
        if ($movementDate === '') throw new InvalidArgumentException('تاريخ الرجوع مطلوب.');
        $dt = str_replace('T', ' ', $movementDate) . ':00';

        $this->pdo->beginTransaction();
        try {
            $status = strtolower((string)$check['current_status']);
            if ($status === 'endorsed' && ((int)($check['last_endorsed_party_id'] ?? 0) > 0)) {
                party_add_ledger($this->pdo, (int)$check['last_endorsed_party_id'], 'CHECK_RETURN_SUP', $id, (string)$check['check_no'], substr($movementDate,0,10), 0.0, (float)$check['amount'], 'رجوع شيك من المورد وإعادة تحميل استحقاقه');
                party_add_ledger($this->pdo, (int)$check['drawer_party_id'], 'CHECK_RETURN_CUST', $id, (string)$check['check_no'], substr($movementDate,0,10), (float)$check['amount'], 0.0, 'رجوع شيك على العميل الأصلي');
                $this->repo->updateCheckStatus($id, 'returned', 'company', null);
                $partyId = (int)$check['last_endorsed_party_id'];
            } else {
                party_add_ledger($this->pdo, (int)$check['drawer_party_id'], 'CHECK_RETURN_CUST', $id, (string)$check['check_no'], substr($movementDate,0,10), (float)$check['amount'], 0.0, 'إرجاع/ارتداد شيك على العميل');
                $this->repo->updateCheckStatus($id, 'returned', 'customer', (int)$check['drawer_party_id']);
                $partyId = (int)$check['drawer_party_id'];
            }
            $this->repo->addMovement($id, 'return', $dt, $partyId, (float)$check['amount'], $notes !== '' ? $notes : null, $userId);
            audit_log('CHECK_RETURN', 'checks', $id, ['status_before'=>$check['current_status']]);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function loadReplaceForm(int $id): array
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_replace_check($check)) throw new InvalidArgumentException('لا يمكن استبدال هذا الشيك في حالته الحالية.');
        return [
            'check' => $check,
            'form' => [
                'replace_date' => date('Y-m-d\TH:i'),
                'new_check_no' => '',
                'bank_name' => (string)($check['bank_name'] ?? ''),
                'issue_date' => date('Y-m-d'),
                'due_date' => '',
                'notes' => '',
            ],
            'errors' => [],
            'page_title' => 'استبدال شيك مرتد',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات','url'=>'/finance/checks'],
                ['label'=>'استبدال شيك'],
            ],
        ];
    }

    public function replace(int $id, array $input, int $userId): int
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_replace_check($check)) throw new InvalidArgumentException('لا يمكن استبدال هذا الشيك في حالته الحالية.');
        $replaceDate = trim((string)($input['replace_date'] ?? date('Y-m-d\TH:i')));
        $newCheckNo = trim((string)($input['new_check_no'] ?? ''));
        $bankName = trim((string)($input['bank_name'] ?? (string)($check['bank_name'] ?? '')));
        $issueDate = trim((string)($input['issue_date'] ?? date('Y-m-d')));
        $dueDate = trim((string)($input['due_date'] ?? ''));
        $notes = trim((string)($input['notes'] ?? ''));
        if ($replaceDate === '') throw new InvalidArgumentException('تاريخ الاستبدال مطلوب.');
        if ($newCheckNo === '') throw new InvalidArgumentException('رقم الشيك الجديد مطلوب.');
        if ($newCheckNo === (string)$check['check_no']) throw new InvalidArgumentException('رقم الشيك الجديد يجب أن يختلف عن الشيك القديم.');
        $dt = str_replace('T', ' ', $replaceDate) . ':00';

        $this->pdo->beginTransaction();
        try {
            $this->repo->updateCheckStatus($id, 'replaced', 'company', null);
            $this->repo->addMovement($id, 'replace', $dt, (int)$check['drawer_party_id'], (float)$check['amount'], $notes !== '' ? $notes : 'استبدال بشيك جديد', $userId);
            $newId = $this->repo->insertReceived([
                'check_no' => $newCheckNo,
                'amount' => (float)$check['amount'],
                'drawer_party_id' => (int)$check['drawer_party_id'],
                'bank_name' => $bankName,
                'issue_date' => $issueDate,
                'due_date' => $dueDate,
                'movement_dt' => $dt,
                'notes' => $notes !== '' ? $notes : ('بديل عن الشيك رقم ' . $check['check_no']),
            ], $userId);
            $this->repo->addMovement($newId, 'receive', $dt, (int)$check['drawer_party_id'], (float)$check['amount'], $notes !== '' ? $notes : ('استلام شيك بديل عن رقم ' . $check['check_no']), $userId);
            party_add_ledger($this->pdo, (int)$check['drawer_party_id'], 'CHECK_REPLACE_NEW', $newId, $newCheckNo, substr($replaceDate,0,10), 0.0, (float)$check['amount'], $notes !== '' ? $notes : 'استلام شيك بديل');
            audit_log('CHECK_REPLACE', 'checks', $id, ['new_check_id'=>$newId, 'new_check_no'=>$newCheckNo]);
            $this->pdo->commit();
            return $newId;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function loadCashSettleForm(int $id): array
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_settle_check_cash($check)) throw new InvalidArgumentException('لا يمكن تسوية هذا الشيك نقدًا في حالته الحالية.');
        $cashboxes = $this->repo->activeCashboxes();
        return [
            'check' => $check,
            'cashboxes' => $cashboxes,
            'form' => ['movement_date' => date('Y-m-d\TH:i'), 'cashbox_id' => (int)($cashboxes[0]['id'] ?? 0), 'notes' => ''],
            'errors' => [],
            'page_title' => 'تسوية شيك مرتد نقدًا',
            'breadcrumbs' => [
                ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                ['label'=>'الشيكات','url'=>'/finance/checks'],
                ['label'=>'تسوية نقدية'],
            ],
        ];
    }

    public function settleCash(int $id, array $input, int $userId): void
    {
        $check = $this->repo->findBasicWithDrawer($id);
        if (!$check) throw new InvalidArgumentException('Check not found');
        if (!can_settle_check_cash($check)) throw new InvalidArgumentException('لا يمكن تسوية هذا الشيك نقدًا في حالته الحالية.');
        $movementDate = trim((string)($input['movement_date'] ?? date('Y-m-d\TH:i')));
        $cashboxId = (int)($input['cashbox_id'] ?? 0);
        $notes = trim((string)($input['notes'] ?? ''));
        if ($movementDate === '') throw new InvalidArgumentException('تاريخ التسوية مطلوب.');
        if ($cashboxId <= 0) throw new InvalidArgumentException('اختر خزنة.');
        $dt = str_replace('T', ' ', $movementDate) . ':00';

        $this->pdo->beginTransaction();
        try {
            cashbox_add_txn($this->pdo, $cashboxId, $dt, 'in', (float)$check['amount'], 'check_cash_settlement', $id, $notes !== '' ? $notes : 'تسوية شيك مرتد نقدًا', $userId);
            party_add_ledger($this->pdo, (int)$check['drawer_party_id'], 'CHECK_SETTLE_CASH', $id, (string)$check['check_no'], substr($movementDate,0,10), 0.0, (float)$check['amount'], $notes !== '' ? $notes : 'تسوية شيك مرتد نقدًا');
            $this->repo->updateCheckStatus($id, 'settled_cash', 'company', null);
            $this->repo->addMovement($id, 'cash_settlement', $dt, (int)$check['drawer_party_id'], (float)$check['amount'], $notes !== '' ? $notes : null, $userId);
            audit_log('CHECK_SETTLE_CASH', 'checks', $id, ['cashbox_id'=>$cashboxId]);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    private function normalizeReceive(array $input): array
    {
        $form = [
            'receive_date' => trim((string)($input['receive_date'] ?? date('Y-m-d\TH:i'))),
            'drawer_party_id' => (int)($input['drawer_party_id'] ?? 0),
            'check_no' => trim((string)($input['check_no'] ?? '')),
            'amount' => trim((string)($input['amount'] ?? '')),
            'bank_name' => trim((string)($input['bank_name'] ?? '')),
            'issue_date' => trim((string)($input['issue_date'] ?? date('Y-m-d'))),
            'due_date' => trim((string)($input['due_date'] ?? '')),
            'notes' => trim((string)($input['notes'] ?? '')),
        ];
        if ($form['drawer_party_id'] <= 0) throw new InvalidArgumentException('يجب اختيار العميل.');
        if ($form['check_no'] === '') throw new InvalidArgumentException('رقم الشيك مطلوب.');
        if ($form['amount'] === '' || !is_numeric($form['amount']) || (float)$form['amount'] <= 0) throw new InvalidArgumentException('قيمة الشيك غير صحيحة.');
        if ($form['receive_date'] === '') throw new InvalidArgumentException('تاريخ الاستلام مطلوب.');
        $form['movement_dt'] = str_replace('T', ' ', $form['receive_date']) . ':00';
        return $form;
    }
}
