<?php

declare(strict_types=1);

namespace App\Modules\Sales\Application\Services;

use App\Repositories\SalesInvoiceRepository;
use App\Services\SalesPostingService;
use PDO;
use Throwable;

final class PosCheckoutService
{
    public function checkout(PDO $pdo, array $form, array $cart, array $cartLines, float $cartTotal, int $userCashboxId, bool $userHasCashbox): array
    {
        if (!$cartLines) {
            throw new \RuntimeException('السلة فارغة. أضف صنفًا واحدًا على الأقل.');
        }
        if ((int)($form['warehouse_id'] ?? 0) <= 0) {
            throw new \RuntimeException('اختر المخزن.');
        }
        if ($userCashboxId <= 0 || !$userHasCashbox) {
            throw new \RuntimeException('هذا المستخدم غير مربوط بخزنة نشطة. اربط المستخدم بخزنة من شاشة المستخدمين أولًا.');
        }

        if ((int)($form['party_id'] ?? 0) <= 0) {
            $form['party_id'] = $this->walkinPartyId($pdo);
            if (($form['customer_label'] ?? '') === '') {
                $form['customer_label'] = 'عميل نقدي POS';
            }
        }

        [$form['discount_type'], $form['discount_value'], $form['discount_amount'], $form['grand_total']] = $this->applyDiscount(
            $cartTotal,
            (string)($form['discount_type'] ?? 'VALUE'),
            (float)($form['discount_value'] ?? 0)
        );
        $form['paid_amount'] = round((float)$form['grand_total'], 2);
        $form['due_amount'] = 0.0;
        $form['cashbox_id'] = $userCashboxId;
        if ((float)($form['received_amount'] ?? 0) <= 0) {
            $form['received_amount'] = (float)$form['paid_amount'];
        }
        if ((float)$form['received_amount'] < (float)$form['paid_amount']) {
            throw new \RuntimeException('المبلغ المستلم من العميل يجب أن يكون مساويًا لصافي الفاتورة أو أكبر منه.');
        }
        $form['change_amount'] = round((float)$form['received_amount'] - (float)$form['paid_amount'], 2);

        $items = $this->loadItemsWithBalances($pdo, (int)$form['warehouse_id'], $cart);
        foreach ($cart as $id => $entryRaw) {
            $entry = (new PosCartService())->normalizeEntry($entryRaw);
            $qty = (float)$entry['qty'];
            if (!isset($items[(int)$id])) {
                throw new \RuntimeException('يوجد صنف غير موجود أو تم تعطيله داخل السلة.');
            }
            if ($qty <= 0) {
                throw new \RuntimeException('يوجد سطر بكمية غير صحيحة داخل السلة.');
            }
        }

        $pdo->beginTransaction();
        try {
            $invoiceNo = $this->generateInvoiceNo($pdo);
            $invoiceId = $this->insertInvoice($pdo, $form, $cartTotal, $invoiceNo);
            $this->insertLinesAndStock($pdo, $form, $cart, $items, $invoiceId);
            $this->postLedgerAndCash($pdo, $form, $invoiceId, $invoiceNo);
            audit_log('POST_POS_SALES_INV', 'sales_invoices', $invoiceId, [
                'invoice_no' => $invoiceNo,
                'warehouse_id' => $form['warehouse_id'],
                'party_id' => $form['party_id'],
                'payment_mode' => $form['payment_mode'],
                'subtotal' => (float)$cartTotal,
                'discount_amount' => (float)$form['discount_amount'],
                'grand_total' => (float)$form['grand_total'],
                'paid_amount' => (float)$form['paid_amount'],
                'due_amount' => (float)$form['due_amount'],
                'cashbox_id' => $form['cashbox_id'],
            ]);
            $pdo->commit();
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }

        return ['invoice_id' => $invoiceId, 'invoice_no' => $invoiceNo, 'form' => $form];
    }

    private function applyDiscount(float $subtotal, string $type, float $value): array
    {
        return (new PosFormService())->applyDiscount($subtotal, $type, $value);
    }

    private function walkinPartyId(PDO $pdo): int
    {
        return (new PosFormService())->walkinPartyId($pdo);
    }

    private function loadItemsWithBalances(PDO $pdo, int $warehouseId, array $cart): array
    {
        $ids = array_map('intval', array_keys($cart));
        if (!$ids) {
            return [];
        }
        $in = implode(',', array_fill(0, count($ids), '?'));
        $st = $pdo->prepare("SELECT i.id, i.name, i.price, i.cost, COALESCE(sb.qty,0) balance
            FROM items i
            LEFT JOIN stock_balances sb ON sb.item_id=i.id AND sb.warehouse_id=?
            WHERE i.id IN ($in) AND i.is_active=1");
        $st->execute(array_merge([$warehouseId], $ids));
        $items = [];
        foreach ($st->fetchAll() as $row) {
            $items[(int)$row['id']] = $row;
        }
        return $items;
    }

    private function generateInvoiceNo(PDO $pdo): string
    {
        return (new SalesInvoiceRepository($pdo))->generateInvoiceNo();
    }

    private function insertInvoice(PDO $pdo, array $form, float $cartTotal, string $invoiceNo): int
    {
        $pdo->prepare("INSERT INTO sales_invoices
            (invoice_no, invoice_date, warehouse_id, customer_id, party_id, status, total, subtotal, discount_type, discount_value, discount_note, discount_amount, net_after_discount, vat_enabled, vat_rate, vat_amount, grand_total, notes, payment_mode, paid_amount, due_amount, cashbox_id, collection_note, user_id, posted_by, posted_at)
            VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
            ->execute([
                $invoiceNo,
                $form['invoice_date'],
                $form['warehouse_id'],
                null,
                $form['party_id'],
                'POSTED',
                (float)$cartTotal,
                (float)$cartTotal,
                $form['discount_type'],
                (float)$form['discount_value'],
                null,
                (float)$form['discount_amount'],
                (float)$form['grand_total'],
                0,
                0.0,
                0.0,
                (float)$form['grand_total'],
                (($form['notes'] ?? '') !== '' ? $form['notes'] : null),
                $form['payment_mode'],
                (float)$form['paid_amount'],
                (float)$form['due_amount'],
                ((int)$form['cashbox_id'] > 0 ? $form['cashbox_id'] : null),
                (($form['collection_note'] ?? '') !== '' ? $form['collection_note'] : null),
                current_user_id(),
                current_user_id(),
                date('Y-m-d H:i:s'),
            ]);
        return (int)$pdo->lastInsertId();
    }

    private function insertLinesAndStock(PDO $pdo, array $form, array $cart, array $items, int $invoiceId): void
    {
        $postingLines = [];
        foreach ($cart as $id => $entryRaw) {
            $entry = (new PosCartService())->normalizeEntry($entryRaw);
            $qty = (float)$entry['qty'];
            $it = $items[(int)$id];
            $price = (float)$it['price'];
            $cost = (float)$it['cost'];
            $baseTotal = round($price * $qty, 2);
            [$lineDiscountType, $lineDiscountValue, $lineDiscountAmount, $lineTotal] = (new PosCartService())->itemDiscount($baseTotal, (string)$entry['discount_type'], (float)$entry['discount_value']);
            $itemName = (string)$it['name'];
            if ($lineDiscountAmount > 0) {
                $itemName .= ' [خصم سطر ' . ($lineDiscountType === 'PERCENT'
                    ? ('%' . rtrim(rtrim(number_format($lineDiscountValue, 2, '.', ''), '0'), '.'))
                    : number_format($lineDiscountValue, 2, '.', '')) . ']';
            }

            $pdo->prepare('INSERT INTO sales_items (invoice_id, item_id, item_name, qty, price, cost, line_total) VALUES (?,?,?,?,?,?,?)')
                ->execute([$invoiceId, (int)$id, $itemName, $qty, $price, $cost, $lineTotal]);
            $postingLines[] = ['item_id' => (int) $id, 'qty' => $qty];
        }

        (new SalesPostingService($pdo))->postStockOutForInvoice($invoiceId, (int) $form['warehouse_id'], $postingLines, $items, current_user_id());
    }

    private function postLedgerAndCash(PDO $pdo, array $form, int $invoiceId, string $invoiceNo): void
    {
        (new SalesPostingService($pdo))->postPartyLedgerAndCash(
            (int) $form['party_id'],
            $invoiceId,
            $invoiceNo,
            (string) $form['invoice_date'],
            (float) $form['grand_total'],
            (float) $form['paid_amount'],
            (int) $form['cashbox_id'],
            (($form['notes'] ?? '') !== '' ? (string) $form['notes'] : null),
            (($form['collection_note'] ?? '') !== '' ? (string) $form['collection_note'] : null),
            current_user_id(),
            true
        );
    }
}
