<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\SchemaRuntime;
use App\Repositories\PurchaseInvoiceRepository;
use App\Shared\Schema\FinanceSchemaSupport;
use App\Shared\Schema\PartySchemaSupport;
use InvalidArgumentException;
use PDO;
use Throwable;

final class PurchaseInvoiceService
{
    public function __construct(private PDO $pdo, private PurchaseInvoiceRepository $repo)
    {
    }

    public function ensureSchemaOrThrow(): void
    {
        SchemaRuntime::ensure($this->pdo, 'purchase_invoices_party_schema', function (): void { PartySchemaSupport::ensurePurchaseInvoicesPartySchema($this->pdo); }, 'sql/migrate_v1_19_parties_links.sql');
        SchemaRuntime::ensure($this->pdo, 'party_ledger_schema', function (): void { PartySchemaSupport::ensurePartyLedgerSchema($this->pdo); }, 'sql/migrate_v1_20_party_ledger.sql');
        SchemaRuntime::ensure($this->pdo, 'cashboxes_schema', function (): void { FinanceSchemaSupport::ensureCashboxesSchema($this->pdo); }, 'sql/migrate_v1_27_cashboxes.sql');
        SchemaRuntime::ensure($this->pdo, 'purchase_invoice_payment_schema', function (): void { FinanceSchemaSupport::ensurePurchaseInvoicePaymentSchema($this->pdo); }, 'sql/migrate_v1_23_purchases.sql');
        if (!table_exists($this->pdo, 'purchase_invoices')) {
            throw new InvalidArgumentException('مطلوب تشغيل ملف التحديث: sql/migrate_v1_23_purchases.sql');
        }
        try { $this->pdo->query('SELECT wht_enabled, wht_rate, wht_amount FROM purchase_invoices LIMIT 1'); } catch (Throwable) {
            throw new InvalidArgumentException('مطلوب تشغيل ملف التحديث: sql/migrate_v1_24_purchase_withholding.sql');
        }
        try { $this->pdo->query('SELECT wht_notice_no FROM purchase_invoices LIMIT 1'); } catch (Throwable) {
            throw new InvalidArgumentException('مطلوب تشغيل ملف التحديث: sql/migrate_v1_25_purchase_wht_notice.sql');
        }
    }

    public function loadForm(int $id): array
    {
        $isEdit = $id > 0;
        $warehouses = $this->pdo->query("SELECT id, name FROM warehouses WHERE is_active=1 ORDER BY name")->fetchAll() ?: [];
        $units = $this->pdo->query("SELECT id, name FROM units ORDER BY name")->fetchAll() ?: [];
        $cashboxes = $this->pdo->query("SELECT id, name FROM cashboxes WHERE is_active=1 ORDER BY name")->fetchAll() ?: [];

        $invoice = null;
        $lines = [];
        if ($isEdit) {
            $invoice = $this->repo->findById($id);
            if (!$invoice) throw new InvalidArgumentException('Not found');
            if (strtoupper((string)$invoice['status']) !== 'DRAFT') throw new InvalidArgumentException('لا يمكن تعديل فاتورة مرحّلة/ملغاة.');
            $lines = $this->repo->fetchEditLines($id);
        }

        $invoice = $invoice ?: [
            'inv_date' => date('Y-m-d'),
            'warehouse_id' => $warehouses[0]['id'] ?? 0,
            'party_id' => 0,
            'supplier_id' => 0,
            'discount_type' => 'PERCENT',
            'discount_value' => 0,
            'discount_note' => '',
            'tax_enabled' => 0,
            'tax_rate' => 14,
            'tax_invoice_no' => '',
            'wht_enabled' => 0,
            'wht_rate' => 1,
            'wht_amount' => 0,
            'wht_notice_no' => '',
            'extra_charges' => 0,
            'charges_note' => '',
            'notes' => '',
            'payment_mode' => 'credit',
            'paid_amount' => 0,
            'due_amount' => 0,
            'cashbox_id' => (int)($cashboxes[0]['id'] ?? 0),
            'payment_note' => '',
            'supplier_label' => '',
        ];

        $old = $_SESSION['purchase_invoice_form_old'] ?? null;
        if (is_array($old)) {
            $invoice = array_merge($invoice, $old['invoice'] ?? []);
            $lines = $old['lines'] ?? $lines;
            unset($_SESSION['purchase_invoice_form_old']);
        }
        $supplier = !empty($invoice['party_id']) ? ($this->repo->fetchSupplier((int)$invoice['party_id']) ?: null) : null;

        return compact('invoice','lines','warehouses','units','cashboxes','supplier','isEdit','id');
    }

    public function save(int $id, array $input, int $userId): int
    {
        $isEdit = $id > 0;
        $payload = $this->buildPayload($input);
        $this->validatePayload($payload);
        $this->pdo->beginTransaction();
        try {
            $saveAction = $payload['save_action'];
            $data = $payload['invoice'];
            $lines = $payload['lines'];
            if (!$isEdit) {
                $data['inv_no'] = $this->repo->generateInvoiceNo();
                $id = $this->repo->create($data, $userId);
                audit_log('CREATE_PURCHASE_INV', 'purchase_invoices', $id, ['inv_no'=>$data['inv_no']]);
            } else {
                $current = $this->repo->findById($id);
                if (!$current) throw new InvalidArgumentException('Not found');
                if (strtoupper((string)$current['status']) !== 'DRAFT') throw new InvalidArgumentException('لا يمكن تعديل فاتورة مرحّلة/ملغاة.');
                $this->repo->update($id, $data);
                $this->repo->deleteDraftSideEffects($id);
                audit_log('UPDATE_PURCHASE_INV', 'purchase_invoices', $id);
            }
            $this->repo->replaceLines($id, $lines);
            if ($saveAction === 'post') {
                $this->postEffects($id, $data, $lines, $userId, $isEdit ? null : $data['inv_no']);
            }
            $this->pdo->commit();
            unset($_SESSION['purchase_invoice_form_old']);
            return $id;
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            $_SESSION['purchase_invoice_form_old'] = $payload;
            throw $e;
        }
    }

    public function postExistingDraft(int $id, int $userId): void
    {
        $this->pdo->beginTransaction();
        try {
            $inv = $this->repo->lock($id);
            if (!$inv) throw new InvalidArgumentException('Not found');
            if (strtoupper((string)$inv['status']) !== 'DRAFT') throw new InvalidArgumentException('لا يمكن ترحيل فاتورة غير مسودة.');
            $lines = $this->repo->fetchEditLines($id);
            if (!$lines) throw new InvalidArgumentException('لا توجد أصناف.');
            $this->postEffects($id, $inv, $lines, $userId, (string)($inv['inv_no'] ?? ''));
            $this->pdo->commit();
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function cancelDraft(int $id): void
    {
        $this->repo->markCanceledDraft($id);
        audit_log('CANCEL_PURCHASE_INV', 'purchase_invoices', $id);
    }

    private function buildPayload(array $input): array
    {
        $partyId = (int)($input['party_id'] ?? ($input['supplier_id'] ?? 0));
        $warehouseId = (int)($input['warehouse_id'] ?? 0);
        $invDate = trim((string)($input['inv_date'] ?? '')) ?: date('Y-m-d');
        $discountType = strtoupper(trim((string)($input['discount_type'] ?? 'PERCENT')));
        if (!in_array($discountType, ['PERCENT','AMOUNT'], true)) $discountType = 'PERCENT';
        $discountValue = (float)($input['discount_value'] ?? 0);
        $taxEnabled = isset($input['tax_enabled']) ? 1 : 0;
        $taxRate = max(0, (float)($input['tax_rate'] ?? 14));
        $whtEnabled = isset($input['wht_enabled']) ? 1 : 0;
        $whtRate = (float)($input['wht_rate'] ?? 1);
        if (!in_array((int)$whtRate, [1,3], true)) $whtRate = 1;
        $extraCharges = max(0, (float)($input['extra_charges'] ?? 0));
        $paymentMode = strtolower(trim((string)($input['payment_mode'] ?? 'credit')));
        if (!in_array($paymentMode, ['cash','credit','partial'], true)) $paymentMode = 'credit';
        $paidAmount = max(0, (float)($input['paid_amount'] ?? 0));
        $cashboxId = (int)($input['cashbox_id'] ?? 0);
        $saveAction = strtolower(trim((string)($input['save_action'] ?? 'draft')));
        if (!in_array($saveAction, ['draft','post'], true)) $saveAction = 'draft';

        [$lines, $subtotal] = $this->normalizeLines($input);
        $tot = $this->calcTotals($subtotal, $discountType, $discountValue, $taxEnabled, $taxRate, $extraCharges, $whtEnabled, $whtRate);
        $grandTotal = (float)$tot['total'];
        if ($paymentMode === 'cash') $paidAmount = $grandTotal;
        elseif ($paymentMode === 'credit') $paidAmount = 0.0;
        elseif ($paidAmount > $grandTotal) $paidAmount = $grandTotal;
        $dueAmount = max(0, round($grandTotal - $paidAmount, 2));

        $invoice = [
            'party_id' => $partyId,
            'warehouse_id' => $warehouseId,
            'inv_date' => $invDate,
            'status' => ($saveAction === 'post') ? 'POSTED' : 'DRAFT',
            'notes' => trim((string)($input['notes'] ?? '')) ?: null,
            'payment_mode' => $paymentMode,
            'paid_amount' => $paidAmount,
            'due_amount' => $dueAmount,
            'cashbox_id' => $cashboxId > 0 ? $cashboxId : null,
            'payment_note' => trim((string)($input['payment_note'] ?? '')) ?: null,
            'discount_type' => $discountType,
            'discount_value' => $discountValue,
            'discount_note' => trim((string)($input['discount_note'] ?? '')) ?: null,
            'tax_enabled' => $taxEnabled,
            'tax_rate' => $taxRate,
            'tax_invoice_no' => trim((string)($input['tax_invoice_no'] ?? '')) ?: null,
            'wht_enabled' => $whtEnabled,
            'wht_rate' => $whtRate,
            'wht_amount' => $tot['wht_amount'],
            'wht_notice_no' => trim((string)($input['wht_notice_no'] ?? '')) ?: null,
            'extra_charges' => $extraCharges,
            'charges_note' => trim((string)($input['charges_note'] ?? '')) ?: null,
            'subtotal' => $tot['subtotal'],
            'discount_amount' => $tot['discount_amount'],
            'tax_amount' => $tot['tax_amount'],
            'total' => $tot['total'],
            'posted_by' => ($saveAction === 'post') ? $userId : null,
            'posted_at' => ($saveAction === 'post') ? date('Y-m-d H:i:s') : null,
            'supplier_label' => trim((string)($input['supplier_label'] ?? '')),
        ];
        return ['invoice' => $invoice, 'lines' => $lines, 'save_action' => $saveAction];
    }

    private function validatePayload(array $payload): void
    {
        $invoice = $payload['invoice'];
        if ((int)$invoice['party_id'] <= 0) throw new InvalidArgumentException('اختر المورد.');
        if ((int)$invoice['warehouse_id'] <= 0) throw new InvalidArgumentException('اختر المخزن.');
        if (!$payload['lines']) throw new InvalidArgumentException('أدخل على الأقل صنف واحد.');
        if (in_array($invoice['payment_mode'], ['cash','partial'], true) && (int)($invoice['cashbox_id'] ?? 0) <= 0) {
            throw new InvalidArgumentException('اختر الخزنة التي سيتم الصرف منها.');
        }
        if ($invoice['payment_mode'] === 'partial') {
            if ((float)$invoice['paid_amount'] <= 0 || (float)$invoice['paid_amount'] >= (float)$invoice['total']) {
                throw new InvalidArgumentException('في الدفع الجزئي يجب أن يكون المدفوع أكبر من صفر وأقل من إجمالي الفاتورة.');
            }
        }
        $ids = array_map(fn($r)=>(int)$r['item_id'], $payload['lines']);
        $map = $this->repo->fetchItemsMap($ids);
        if ($payload['save_action'] === 'post') {
            foreach ($payload['lines'] as $ln) {
                $it = $map[(int)$ln['item_id']] ?? null;
                if (!$it || !(int)$it['is_active']) throw new InvalidArgumentException('يوجد صنف غير موجود أو تم تعطيله. راجع سطور الفاتورة ثم حاول مرة أخرى.');
            }
        }
    }

    private function normalizeLines(array $input): array
    {
        $itemIds = $input['item_id'] ?? [];
        $unitIds = $input['unit_id'] ?? [];
        $qtys = $input['qty'] ?? [];
        $prices = $input['unit_price'] ?? [];
        $labels = $input['item_name'] ?? [];
        $newLines = [];
        $subtotal = 0.0;
        $seen = [];
        $count = count($itemIds);
        for ($i=0; $i<$count; $i++) {
            $iid = (int)($itemIds[$i] ?? 0);
            $uid = (int)($unitIds[$i] ?? 0);
            $qty = (float)($qtys[$i] ?? 0);
            $price = max(0, (float)($prices[$i] ?? 0));
            $label = trim((string)($labels[$i] ?? ''));
            if ($iid <= 0 && $label === '' && $qty <= 0 && $price <= 0) continue;
            if ($iid <= 0) throw new InvalidArgumentException('يوجد سطر به صنف غير صحيح أو لم يتم اختياره من القائمة. اختر الصنف الصحيح ثم أعد المحاولة.');
            if ($qty <= 0) throw new InvalidArgumentException('الكمية يجب أن تكون أكبر من صفر في كل سطر.');
            if (isset($seen[$iid])) throw new InvalidArgumentException('لا يمكن تكرار نفس الصنف أكثر من مرة داخل نفس الفاتورة. عدّل الكمية في السطر الحالي بدل إضافة سطر جديد.');
            $seen[$iid] = true;
            $lineTotal = $qty * $price;
            $subtotal += $lineTotal;
            $newLines[] = ['item_id'=>$iid,'item_name'=>$label,'unit_id'=>$uid>0?$uid:null,'qty'=>$qty,'unit_price'=>$price,'line_total'=>$lineTotal];
        }
        return [$newLines, $subtotal];
    }

    private function calcTotals(float $subtotal, string $discountType, float $discountValue, int $vatEnabled, float $vatRate, float $extraCharges, int $whtEnabled, float $whtRate): array
    {
        $discountAmount = strtoupper($discountType) === 'PERCENT' ? $subtotal * (max(0,$discountValue) / 100.0) : max(0,$discountValue);
        if ($discountAmount > $subtotal) $discountAmount = $subtotal;
        $net = $subtotal - $discountAmount;
        $vatAmount = $vatEnabled ? max(0, $net * ($vatRate/100.0)) : 0.0;
        $whtAmount = $whtEnabled ? max(0, $net * ($whtRate/100.0)) : 0.0;
        $total = $net + max(0.0, $extraCharges) + $vatAmount - $whtAmount;
        return [
            'subtotal' => round($subtotal, 2),
            'discount_amount' => round($discountAmount, 2),
            'tax_amount' => round($vatAmount, 2),
            'wht_amount' => round($whtAmount, 2),
            'total' => round($total, 2),
        ];
    }

    private function postEffects(int $id, array $invoice, array $lines, int $userId, ?string $docNo): void
    {
        require_permission('purchases.post');
        foreach ($lines as $ln) {
            $itemId = (int)$ln['item_id'];
            $unitId = (int)($ln['unit_id'] ?? 0);
            $qty = (float)$ln['qty'];
            $price = (float)$ln['unit_price'];
            $factor = item_unit_factor($this->pdo, $itemId, $unitId > 0 ? $unitId : null);
            $qtyBase = $qty * $factor;
            $this->pdo->prepare("INSERT INTO stock_moves (ref_type, ref_id, warehouse_id, item_id, qty_in, qty_out, cost, user_id) VALUES ('PURCHASE_INV', ?, ?, ?, ?, 0, ?, ?)")
                ->execute([$id, (int)$invoice['warehouse_id'], $itemId, $qtyBase, $price, $userId]);
            stock_balance_apply($this->pdo, (int)$invoice['warehouse_id'], $itemId, $qtyBase);
        }
        $docNo = $docNo ?: ('PI#' . $id);
        $partyId = (int)($invoice['party_id'] ?? $invoice['supplier_id'] ?? 0);
        if ($partyId > 0) {
            $this->pdo->prepare("DELETE FROM party_ledger WHERE doc_type IN ('PURCHASE_INV','PURCHASE_PAY') AND doc_id=?")->execute([$id]);
            party_add_ledger($this->pdo, $partyId, 'PURCHASE_INV', $id, $docNo, (string)$invoice['inv_date'], 0.0, (float)$invoice['total'], trim((string)($invoice['notes'] ?? '')) !== '' ? (string)$invoice['notes'] : 'فاتورة شراء');
            $paidAmount = (float)($invoice['paid_amount'] ?? 0);
            if ($paidAmount > 0) {
                $payNotes = trim((string)($invoice['payment_note'] ?? '')) !== '' ? (string)$invoice['payment_note'] : ('سداد على فاتورة الشراء ' . $docNo);
                party_add_ledger($this->pdo, $partyId, 'PURCHASE_PAY', $id, $docNo, (string)$invoice['inv_date'], $paidAmount, 0.0, $payNotes);
                if (!empty($invoice['cashbox_id'])) {
                    if (table_exists($this->pdo, 'cashbox_transactions')) {
                        $this->pdo->prepare("DELETE FROM cashbox_transactions WHERE ref_type='purchase_invoice' AND ref_id=?")->execute([$id]);
                    }
                    cashbox_add_txn($this->pdo, (int)$invoice['cashbox_id'], date('Y-m-d H:i:s'), 'out', $paidAmount, 'purchase_invoice', $id, $payNotes, $userId);
                }
            }
        }
        $this->repo->markPosted($id, $userId);
        audit_log('POST_PURCHASE_INV', 'purchase_invoices', $id, ['inv_no'=>$docNo]);
    }
}
