<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\SalesInvoiceRepository;
use App\Services\SalesPostingService;
use InvalidArgumentException;
use PDO;
use RuntimeException;

final class SalesInvoiceService
{
    public function __construct(private PDO $pdo, private SalesInvoiceRepository $salesInvoices)
    {
    }

    public function defaultInvoice(array $warehouses, array $cashboxes): array
    {
        return [
            'invoice_date' => date('Y-m-d'),
            'warehouse_id' => $warehouses[0]['id'] ?? 0,
            'party_id' => 0,
            'customer_id' => 0,
            'discount_type' => 'PERCENT',
            'discount_value' => 0,
            'discount_note' => '',
            'vat_enabled' => 0,
            'vat_rate' => 14,
            'notes' => '',
            'payment_mode' => 'credit',
            'paid_amount' => 0,
            'due_amount' => 0,
            'cashbox_id' => (int)($cashboxes[0]['id'] ?? 0),
            'collection_note' => '',
        ];
    }

    public function calcTotals(float $subtotal, string $discountType, float $discountValue, int $vatEnabled, float $vatRate): array
    {
        $discountType = strtoupper($discountType);
        if ($discountValue < 0) {
            $discountValue = 0;
        }
        $discountAmount = $discountType === 'PERCENT' ? $subtotal * ($discountValue / 100.0) : $discountValue;
        if ($discountAmount > $subtotal) {
            $discountAmount = $subtotal;
        }
        $net = $subtotal - $discountAmount;
        $vatAmount = 0.0;
        if ($vatEnabled) {
            if ($vatRate < 0) {
                $vatRate = 0;
            }
            $vatAmount = $net * ($vatRate / 100.0);
        }
        return [
            'subtotal' => $subtotal,
            'discount_amount' => $discountAmount,
            'net_after_discount' => $net,
            'vat_amount' => $vatAmount,
            'grand_total' => $net + $vatAmount,
        ];
    }

    public function save(int $id, bool $isEdit, array $input, ?int $currentUserId): int
    {
        $invoiceDate = trim((string)($input['invoice_date'] ?? date('Y-m-d')));
        $warehouseId = (int)($input['warehouse_id'] ?? 0);
        $partyId = (int)($input['party_id'] ?? ($input['customer_id'] ?? 0));
        $discountType = strtoupper(trim((string)($input['discount_type'] ?? 'PERCENT')));
        if (!in_array($discountType, ['PERCENT','VALUE'], true)) {
            $discountType = 'PERCENT';
        }
        $discountValue = (float)($input['discount_value'] ?? 0);
        $discountNote = trim((string)($input['discount_note'] ?? ''));
        $vatEnabled = isset($input['vat_enabled']) ? 1 : 0;
        $vatRate = (float)($input['vat_rate'] ?? 14);
        $notes = trim((string)($input['notes'] ?? ''));
        $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);
        $collectionNote = trim((string)($input['collection_note'] ?? ''));
        $action = (string)($input['action'] ?? 'save');

        [$cleanLines, $subtotal] = $this->normalizeLines($input);

        if ($partyId <= 0) {
            throw new InvalidArgumentException('اختر الطرف / العميل');
        }
        if ($warehouseId <= 0) {
            throw new InvalidArgumentException('اختر المخزن');
        }
        if (!$cleanLines) {
            throw new InvalidArgumentException('أضف صنفًا واحدًا على الأقل');
        }

        $tot = $this->calcTotals($subtotal, $discountType, $discountValue, $vatEnabled, $vatRate);
        $grandTotal = (float) $tot['grand_total'];
        if ($paymentMode === 'cash') {
            $paidAmount = $grandTotal;
        } elseif ($paymentMode === 'credit') {
            $paidAmount = 0.0;
        } elseif ($paidAmount > $grandTotal) {
            $paidAmount = $grandTotal;
        }
        $dueAmount = max(0, round($grandTotal - $paidAmount, 2));
        if (in_array($paymentMode, ['cash','partial'], true) && $cashboxId <= 0) {
            throw new InvalidArgumentException('اختر الخزنة المحصل عليها.');
        }
        if ($paymentMode === 'partial' && ($paidAmount <= 0 || $paidAmount >= $grandTotal)) {
            throw new InvalidArgumentException('في الدفع الجزئي يجب أن يكون المدفوع أكبر من صفر وأقل من إجمالي الفاتورة.');
        }

        if ($action === 'post') {
            $cust = $this->salesInvoices->fetchCustomerCreditMeta($partyId);
            $creditLimit = $cust ? (float)$cust['credit_limit'] : 0.0;
            if ($creditLimit > 0) {
                $bal = party_balance($this->pdo, $partyId, $invoiceDate);
                $after = $bal + $dueAmount;
                if ($after > $creditLimit + 0.0001) {
                    throw new InvalidArgumentException('لا يمكن ترحيل الفاتورة: العميل تجاوز الحد الائتماني. (الرصيد الحالي: ' . format_money($bal) . ' / الحد: ' . format_money($creditLimit) . ')');
                }
            }
        }

        $itemsMap = $this->salesInvoices->fetchItemsStockMap($warehouseId, array_map(fn(array $r): int => (int)$r['item_id'], $cleanLines));
        foreach ($cleanLines as $ln) {
            $iid = (int)$ln['item_id'];
            if (!isset($itemsMap[$iid])) {
                throw new InvalidArgumentException('يوجد صنف غير موجود أو تم تعطيله. راجع سطور الفاتورة ثم حاول مرة أخرى.');
            }
        }

        try {
            $this->pdo->beginTransaction();
            $status = ($action === 'post') ? 'POSTED' : 'DRAFT';
            $payload = [
                'invoice_date' => $invoiceDate,
                'warehouse_id' => $warehouseId,
                'party_id' => $partyId,
                'status' => $status,
                'subtotal' => (float)$tot['subtotal'],
                'discount_type' => $discountType,
                'discount_value' => (float)$discountValue,
                'discount_note' => $discountNote,
                'discount_amount' => (float)$tot['discount_amount'],
                'net_after_discount' => (float)$tot['net_after_discount'],
                'vat_enabled' => (int)$vatEnabled,
                'vat_rate' => (float)$vatRate,
                'vat_amount' => (float)$tot['vat_amount'],
                'grand_total' => $grandTotal,
                'notes' => $notes,
                'payment_mode' => $paymentMode,
                'paid_amount' => (float)$paidAmount,
                'due_amount' => (float)$dueAmount,
                'cashbox_id' => $cashboxId > 0 ? $cashboxId : null,
                'collection_note' => $collectionNote !== '' ? $collectionNote : null,
                'posted_by' => ($action === 'post') ? $currentUserId : null,
                'posted_at' => ($action === 'post') ? date('Y-m-d H:i:s') : null,
            ];

            if ($isEdit) {
                $lockedInvoice = $this->salesInvoices->lock($id);
                if (!$lockedInvoice) {
                    throw new RuntimeException('الفاتورة غير موجودة.');
                }
                if ($action === 'post' && strtoupper((string) ($lockedInvoice['status'] ?? '')) !== 'DRAFT') {
                    throw new RuntimeException('لا يمكن ترحيل فاتورة غير مسودة أو تم ترحيلها مسبقًا.');
                }
                $this->salesInvoices->update($id, $payload);
            } else {
                $payload['invoice_no'] = $this->salesInvoices->generateInvoiceNo();
                $payload['user_id'] = $currentUserId;
                $id = $this->salesInvoices->create($payload);
                audit_log('CREATE_SALES_INV', 'sales_invoices', $id, ['invoice_no' => $payload['invoice_no']]);
                $isEdit = true;
            }

            $this->salesInvoices->replaceLines($id, $cleanLines, $itemsMap);

            if ($action === 'post') {
                $this->salesInvoices->deletePostingEffects($id);
                $posting = new SalesPostingService($this->pdo);
                $posting->postStockOutForInvoice($id, $warehouseId, $cleanLines, $itemsMap, $currentUserId);

                $docNo = $this->salesInvoices->fetchInvoiceNo($id);
                $posting->postPartyLedgerAndCash($partyId, $id, $docNo, $invoiceDate, $grandTotal, (float) $paidAmount, $cashboxId, $notes, $collectionNote, $currentUserId, false);
                audit_log('POST_SALES_INV', 'sales_invoices', $id, ['grand_total' => $grandTotal, 'payment_mode' => $paymentMode, 'paid_amount' => (float)$paidAmount, 'due_amount' => (float)$dueAmount, 'cashbox_id' => $cashboxId]);
            }

            $this->pdo->commit();
            return $id;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }



    public function deleteDraft(int $id, ?int $currentUserId = null): void
    {
        if ($id <= 0) {
            throw new InvalidArgumentException('فاتورة غير صحيحة.');
        }

        try {
            $this->pdo->beginTransaction();
            $invoice = $this->salesInvoices->lock($id);
            if (!$invoice) {
                throw new InvalidArgumentException('الفاتورة غير موجودة.');
            }
            if (strtoupper((string)($invoice['status'] ?? '')) !== 'DRAFT') {
                throw new InvalidArgumentException('يمكن حذف الفاتورة المسودة فقط.');
            }

            $deleted = $this->salesInvoices->deleteDraft($id);
            if (!$deleted) {
                throw new RuntimeException('تعذر حذف الفاتورة المسودة.');
            }

            audit_log('DELETE_DRAFT_SALES_INV', 'sales_invoices', $id, [
                'invoice_no' => (string)($invoice['invoice_no'] ?? ''),
                'status' => (string)($invoice['status'] ?? ''),
                'deleted_by' => $currentUserId,
            ]);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }

    private function normalizeLines(array $input): array
    {
        $itemIds = $input['item_id'] ?? [];
        $qtys = $input['qty'] ?? [];
        $prices = $input['price'] ?? [];
        $itemLabels = $input['item_name'] ?? [];
        $cleanLines = [];
        $subtotal = 0.0;
        $seen = [];
        $count = count($itemIds);
        for ($i = 0; $i < $count; $i++) {
            $iid = (int)($itemIds[$i] ?? 0);
            $qty = (float)($qtys[$i] ?? 0);
            $price = max(0, (float)($prices[$i] ?? 0));
            $label = trim((string)($itemLabels[$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;
            $cleanLines[] = ['item_id' => $iid, 'qty' => $qty, 'price' => $price, 'line_total' => $lineTotal];
        }
        return [$cleanLines, $subtotal];
    }
}
