<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\StockVoucherRepository;
use InvalidArgumentException;
use PDO;
use RuntimeException;

final class StockVoucherService
{
    public function __construct(private PDO $pdo, private StockVoucherRepository $repo) {}

    public function initSessionState(): void
    {
        $_SESSION['wh_doc_lines'] ??= [];
    }

    public function getState(array $input = []): array
    {
        $docType = (string)($input['doc_type'] ?? ($_SESSION['wh_doc_type'] ?? 'RECEIPT'));
        $warehouseId = (int)($input['warehouse_id'] ?? ($_SESSION['wh_doc_wh'] ?? main_warehouse_id($this->pdo)));
        $toWarehouseId = (int)($input['to_warehouse_id'] ?? ($_SESSION['wh_doc_to'] ?? 0));
        $docDate = (string)($input['doc_date'] ?? ($_SESSION['wh_doc_date'] ?? date('Y-m-d')));
        $notes = (string)($input['notes'] ?? ($_SESSION['wh_doc_notes'] ?? ''));
        $_SESSION['wh_doc_type'] = $docType;
        $_SESSION['wh_doc_wh'] = $warehouseId;
        $_SESSION['wh_doc_to'] = $toWarehouseId;
        $_SESSION['wh_doc_date'] = $docDate;
        $_SESSION['wh_doc_notes'] = $notes;
        return compact('docType','warehouseId','toWarehouseId','docDate','notes');
    }

    public function addLine(int $itemId, float $qty, ?int $unitId): void
    {
        if ($itemId <= 0 || $qty <= 0) throw new InvalidArgumentException('اختر صنف وكمية صحيحة');
        $_SESSION['wh_doc_lines'][] = [
            'item_id'=>$itemId,
            'qty'=>$qty,
            'unit_cost'=>$this->repo->itemCost($itemId),
            'unit_id'=>$unitId,
        ];
    }

    public function removeLine(int $idx): void
    {
        if ($idx >= 0 && $idx < count($_SESSION['wh_doc_lines'])) array_splice($_SESSION['wh_doc_lines'], $idx, 1);
    }

    public function updateLine(string $docType, int $idx, float $qty, ?int $unitId): void
    {
        $lines = $_SESSION['wh_doc_lines'] ?? [];
        if ($idx < 0 || $idx >= count($lines)) throw new RuntimeException('سطر غير موجود');
        if ($docType !== 'ADJUST' && $qty <= 0) throw new RuntimeException('الكمية يجب أن تكون أكبر من صفر');
        if ($docType === 'ADJUST' && $qty == 0.0) throw new RuntimeException('الكمية لا يمكن أن تكون صفر');
        $_SESSION['wh_doc_lines'][$idx]['qty'] = $qty;
        $_SESSION['wh_doc_lines'][$idx]['unit_id'] = $unitId;
    }

    public function clear(): void { $_SESSION['wh_doc_lines'] = []; }

    public function post(array $state): int
    {
        $lines = $_SESSION['wh_doc_lines'] ?? [];
        if (!$lines) throw new RuntimeException('لا توجد بنود');
        $docType = $state['docType']; $warehouseId = $state['warehouseId']; $toWarehouseId = $state['toWarehouseId'];
        if ($docType === 'TRANSFER' && $toWarehouseId <= 0) throw new RuntimeException('اختر مخزن التحويل إليه');

        $outTypes = ['ISSUE','SCRAP','TRANSFER'];
        if (in_array($docType, $outTypes, true) && !warehouse_allows_negative($this->pdo, $warehouseId)) {
            $need = [];
            foreach ($lines as $ln) {
                $iid = (int)$ln['item_id'];
                $u = (int)($ln['unit_id'] ?? 0);
                $f = item_unit_factor($this->pdo, $iid, $u>0?$u:null);
                $need[$iid] = ($need[$iid] ?? 0) + ((float)$ln['qty'] * $f);
            }
            $have = $this->repo->balancesForItems($warehouseId, array_keys($need));
            foreach ($need as $iid => $q) {
                $h = $have[$iid] ?? 0.0;
                if ($h < $q) throw new RuntimeException("رصيد غير كافي للصنف: {$this->repo->itemName((int)$iid)} (متاح $h، مطلوب $q)");
            }
        }

        $prefix = match($docType) {
            'RECEIPT' => 'GRN', 'ISSUE' => 'GIN', 'TRANSFER' => 'TRN', 'SCRAP' => 'SCR', 'ADJUST' => 'ADJ', default => 'DOC'
        };
        $docNo = $this->repo->nextDocNo($prefix);

        $this->pdo->beginTransaction();
        try {
            $docId = $this->repo->createDoc([
                'doc_no' => $docNo,
                'doc_date' => $state['docDate'] . ' 00:00:00',
                'doc_type' => $docType,
                'warehouse_id' => $warehouseId,
                'to_warehouse_id' => ($docType === 'TRANSFER' ? $toWarehouseId : null),
                'notes' => $state['notes'] ?: null,
                'created_by' => current_user_id(),
            ]);

            $itemIds = array_values(array_unique(array_map(fn($ln) => (int)$ln['item_id'], $lines)));
            $costMap = [];
            if ($itemIds) {
                $in = implode(',', array_fill(0, count($itemIds), '?'));
                $st = $this->pdo->prepare("SELECT id, cost FROM items WHERE id IN ($in)");
                $st->execute($itemIds);
                foreach ($st->fetchAll() as $r) $costMap[(int)$r['id']] = (float)$r['cost'];
            }

            $lineNo = 1;
            foreach ($lines as $ln) {
                $itemId = (int)$ln['item_id'];
                $qty = (float)$ln['qty'];
                $unitId = (int)($ln['unit_id'] ?? 0);
                $factor = item_unit_factor($this->pdo, $itemId, $unitId>0?$unitId:null);
                $qtyBase = $qty * $factor;
                $cost = $costMap[$itemId] ?? (float)($ln['unit_cost'] ?? 0);
                $this->repo->createLine($docId, $lineNo++, $itemId, ($unitId>0?$unitId:null), $qty, $cost);

                if ($docType === 'RECEIPT') {
                    $this->repo->insertMove('RECEIPT', $docId, $warehouseId, $itemId, $qtyBase, 0, $cost, current_user_id());
                    stock_balance_apply($this->pdo, $warehouseId, $itemId, $qtyBase);
                } elseif ($docType === 'ISSUE') {
                    $this->repo->insertMove('ISSUE', $docId, $warehouseId, $itemId, 0, $qtyBase, $cost, current_user_id());
                    if (warehouse_allows_negative($this->pdo, $warehouseId)) {
                        if (warehouse_allows_negative($this->pdo, $warehouseId)) {
                        if (warehouse_allows_negative($this->pdo, $warehouseId)) {
                        stock_balance_apply($this->pdo, $warehouseId, $itemId, -$qtyBase);
                    } else {
                        stock_balance_deduct_or_fail($this->pdo, $warehouseId, $itemId, $qtyBase);
                    }
                    } else {
                        stock_balance_deduct_or_fail($this->pdo, $warehouseId, $itemId, $qtyBase);
                    }
                    } else {
                        stock_balance_deduct_or_fail($this->pdo, $warehouseId, $itemId, $qtyBase);
                    }
                } elseif ($docType === 'SCRAP') {
                    $this->repo->insertMove('SCRAP', $docId, $warehouseId, $itemId, 0, $qtyBase, $cost, current_user_id());
                    stock_balance_apply($this->pdo, $warehouseId, $itemId, -$qtyBase);
                } elseif ($docType === 'TRANSFER') {
                    $this->repo->insertMove('TR_OUT', $docId, $warehouseId, $itemId, 0, $qtyBase, $cost, current_user_id());
                    stock_balance_apply($this->pdo, $warehouseId, $itemId, -$qtyBase);
                    $this->repo->insertMove('TR_IN', $docId, $toWarehouseId, $itemId, $qtyBase, 0, $cost, current_user_id());
                    stock_balance_apply($this->pdo, $toWarehouseId, $itemId, $qtyBase);
                } elseif ($docType === 'ADJUST') {
                    if ($qty >= 0) {
                        $this->repo->insertMove('ADJ_IN', $docId, $warehouseId, $itemId, $qtyBase, 0, $cost, current_user_id());
                        stock_balance_apply($this->pdo, $warehouseId, $itemId, $qtyBase);
                    } else {
                        $qout = abs($qtyBase);
                        $this->repo->insertMove('ADJ_OUT', $docId, $warehouseId, $itemId, 0, $qout, $cost, current_user_id());
                        if (warehouse_allows_negative($this->pdo, $warehouseId)) {
                            stock_balance_apply($this->pdo, $warehouseId, $itemId, -$qout);
                        } else {
                            stock_balance_deduct_or_fail($this->pdo, $warehouseId, $itemId, $qout);
                        }
                    }
                }
            }

            $this->pdo->commit();
            $_SESSION['wh_doc_lines'] = [];
            return $docId;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }
}
