<?php
declare(strict_types=1);

namespace App\Modules\Procurement\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;

final class RequisitionService
{
    /**
     * Create a new purchase requisition with its items.
     *
     * @param array $header  Keys: department, urgency, required_date, justification, notes, branch_id
     * @param array $items   Each: ['item_id' => ?int, 'description_ar' => ?string, 'quantity' => string, 'unit_of_measure' => ?string, 'estimated_unit_cost' => ?string, 'specifications' => ?string, 'preferred_supplier_id' => ?int]
     * @return int PR ID
     */
    public static function createPR(array $header, array $items): int
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        if (empty($items)) {
            throw new \RuntimeException('يجب إضافة صنف واحد على الأقل');
        }

        $prNumber = ProcurementNumberGenerator::nextPRNumber();

        $estimatedTotal = '0.00';
        foreach ($items as $item) {
            $qty  = (string) ($item['quantity'] ?? '0');
            $cost = (string) ($item['estimated_unit_cost'] ?? '0');
            if (bccomp($cost, '0', 2) > 0) {
                $lineTotal = bcmul($qty, $cost, 2);
                $estimatedTotal = bcadd($estimatedTotal, $lineTotal, 2);
            }
        }

        $db->beginTransaction();
        try {
            $prId = $db->insert('purchase_requisitions', [
                'pr_number'       => $prNumber,
                'requested_by'    => $employee ? (int) $employee->id : null,
                'department'      => $header['department'] ?? null,
                'status'          => 'draft',
                'urgency'         => $header['urgency'] ?? 'normal',
                'required_date'   => !empty($header['required_date']) ? $header['required_date'] : null,
                'justification'   => $header['justification'] ?? null,
                'estimated_total' => $estimatedTotal,
                'notes'           => $header['notes'] ?? null,
                'branch_id'       => !empty($header['branch_id']) ? (int) $header['branch_id'] : null,
                'created_by'      => $employee ? (int) $employee->id : null,
                'created_at'      => date('Y-m-d H:i:s'),
                'updated_at'      => date('Y-m-d H:i:s'),
            ]);

            foreach ($items as $item) {
                $qty  = (string) ($item['quantity'] ?? '0');
                $cost = (string) ($item['estimated_unit_cost'] ?? '0');
                $lineTotal = bccomp($cost, '0', 2) > 0 ? bcmul($qty, $cost, 2) : null;

                $db->insert('purchase_requisition_items', [
                    'requisition_id'        => $prId,
                    'item_id'               => !empty($item['item_id']) ? (int) $item['item_id'] : null,
                    'description_ar'        => $item['description_ar'] ?? null,
                    'description_en'        => $item['description_en'] ?? null,
                    'quantity'              => $qty,
                    'unit_of_measure'       => $item['unit_of_measure'] ?? null,
                    'estimated_unit_cost'   => bccomp($cost, '0', 2) > 0 ? $cost : null,
                    'estimated_line_total'  => $lineTotal,
                    'specifications'        => $item['specifications'] ?? null,
                    'preferred_supplier_id' => !empty($item['preferred_supplier_id']) ? (int) $item['preferred_supplier_id'] : null,
                ]);
            }

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }

        Logger::info("PR #{$prId} ({$prNumber}) created, estimated total: {$estimatedTotal}");
        return $prId;
    }

    /**
     * Update a draft PR.
     */
    public static function updatePR(int $prId, array $header, array $items): void
    {
        $db = App::getInstance()->db();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if ($pr['status'] !== 'draft') {
            throw new \RuntimeException('لا يمكن تعديل طلب الشراء إلا في حالة المسودة');
        }

        if (empty($items)) {
            throw new \RuntimeException('يجب إضافة صنف واحد على الأقل');
        }

        $estimatedTotal = '0.00';
        foreach ($items as $item) {
            $qty  = (string) ($item['quantity'] ?? '0');
            $cost = (string) ($item['estimated_unit_cost'] ?? '0');
            if (bccomp($cost, '0', 2) > 0) {
                $lineTotal = bcmul($qty, $cost, 2);
                $estimatedTotal = bcadd($estimatedTotal, $lineTotal, 2);
            }
        }

        $db->beginTransaction();
        try {
            $db->update('purchase_requisitions', [
                'department'      => $header['department'] ?? null,
                'urgency'         => $header['urgency'] ?? 'normal',
                'required_date'   => !empty($header['required_date']) ? $header['required_date'] : null,
                'justification'   => $header['justification'] ?? null,
                'estimated_total' => $estimatedTotal,
                'notes'           => $header['notes'] ?? null,
                'updated_at'      => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$prId]);

            // Delete old items and re-insert
            $db->delete('purchase_requisition_items', '`requisition_id` = ?', [$prId]);

            foreach ($items as $item) {
                $qty  = (string) ($item['quantity'] ?? '0');
                $cost = (string) ($item['estimated_unit_cost'] ?? '0');
                $lineTotal = bccomp($cost, '0', 2) > 0 ? bcmul($qty, $cost, 2) : null;

                $db->insert('purchase_requisition_items', [
                    'requisition_id'        => $prId,
                    'item_id'               => !empty($item['item_id']) ? (int) $item['item_id'] : null,
                    'description_ar'        => $item['description_ar'] ?? null,
                    'description_en'        => $item['description_en'] ?? null,
                    'quantity'              => $qty,
                    'unit_of_measure'       => $item['unit_of_measure'] ?? null,
                    'estimated_unit_cost'   => bccomp($cost, '0', 2) > 0 ? $cost : null,
                    'estimated_line_total'  => $lineTotal,
                    'specifications'        => $item['specifications'] ?? null,
                    'preferred_supplier_id' => !empty($item['preferred_supplier_id']) ? (int) $item['preferred_supplier_id'] : null,
                ]);
            }

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }

        Logger::info("PR #{$prId} updated, estimated total: {$estimatedTotal}");
    }

    /**
     * Submit a draft PR for approval.
     */
    public static function submitPR(int $prId): void
    {
        $db = App::getInstance()->db();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if ($pr['status'] !== 'draft') {
            throw new \RuntimeException('طلب الشراء ليس في حالة مسودة');
        }

        $db->update('purchase_requisitions', [
            'status'     => 'submitted',
            'updated_at' => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$prId]);

        EventBus::dispatch('procurement.requisition_submitted', ['pr_id' => $prId]);
        Logger::info("PR #{$prId} submitted for approval");
    }

    /**
     * Approve a submitted PR.
     */
    public static function approvePR(int $prId): void
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if ($pr['status'] !== 'submitted') {
            throw new \RuntimeException('طلب الشراء ليس في حالة مُقدّم');
        }

        $db->update('purchase_requisitions', [
            'status'      => 'approved',
            'approved_by' => $employee ? (int) $employee->id : null,
            'approved_at' => date('Y-m-d H:i:s'),
            'updated_at'  => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$prId]);

        EventBus::dispatch('procurement.requisition_approved', ['pr_id' => $prId]);
        Logger::info("PR #{$prId} approved");
    }

    /**
     * Reject a submitted PR.
     */
    public static function rejectPR(int $prId, string $reason): void
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if ($pr['status'] !== 'submitted') {
            throw new \RuntimeException('طلب الشراء ليس في حالة مُقدّم');
        }

        $db->update('purchase_requisitions', [
            'status'           => 'rejected',
            'rejected_by'      => $employee ? (int) $employee->id : null,
            'rejected_at'      => date('Y-m-d H:i:s'),
            'rejection_reason' => $reason,
            'updated_at'       => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$prId]);

        Logger::info("PR #{$prId} rejected: {$reason}");
    }

    /**
     * Convert an approved PR to a Purchase Order.
     */
    public static function convertToPO(int $prId, array $poHeader): int
    {
        $db = App::getInstance()->db();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if ($pr['status'] !== 'approved') {
            throw new \RuntimeException('طلب الشراء ليس في حالة معتمد');
        }

        $prItems = $db->select(
            "SELECT * FROM `purchase_requisition_items` WHERE `requisition_id` = ?",
            [$prId]
        );

        if (empty($prItems)) {
            throw new \RuntimeException('لا توجد أصناف في طلب الشراء');
        }

        $supplierId  = (int) ($poHeader['supplier_id'] ?? 0);
        $warehouseId = (int) ($poHeader['warehouse_id'] ?? 0);

        if ($supplierId <= 0) {
            throw new \RuntimeException('يجب تحديد المورد');
        }
        if ($warehouseId <= 0) {
            throw new \RuntimeException('يجب تحديد المخزن');
        }

        // Build PO items from PR items
        $poItems = [];
        foreach ($prItems as $pri) {
            if (empty($pri['item_id'])) {
                continue; // skip free-text items that don't map to inventory
            }
            $poItems[] = [
                'item_id'          => (int) $pri['item_id'],
                'quantity_ordered' => (string) $pri['quantity'],
                'unit_price'       => (string) ($pri['estimated_unit_cost'] ?? '0'),
                'notes'            => $pri['specifications'] ?? null,
            ];
        }

        if (empty($poItems)) {
            throw new \RuntimeException('لا توجد أصناف قابلة للتحويل لأمر شراء');
        }

        // Use PurchaseOrderService to create the PO
        $poService = \App\Modules\Inventory\Services\PurchaseOrderService::class;
        $poId = $poService::createPO([
            'supplier_id'            => $supplierId,
            'warehouse_id'           => $warehouseId,
            'expected_delivery_date' => $poHeader['expected_delivery_date'] ?? $pr['required_date'],
            'notes'                  => $poHeader['notes'] ?? $pr['notes'],
        ], $poItems);

        // Link PO back to PR
        $db->update('purchase_orders', [
            'requisition_id' => $prId,
        ], '`id` = ?', [$poId]);

        // Update PR status
        $db->update('purchase_requisitions', [
            'status'          => 'converted',
            'converted_po_id' => $poId,
            'updated_at'      => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$prId]);

        EventBus::dispatch('procurement.requisition_converted', [
            'pr_id' => $prId,
            'po_id' => $poId,
        ]);

        Logger::info("PR #{$prId} converted to PO #{$poId}");
        return $poId;
    }

    /**
     * Cancel a PR (draft or submitted only).
     */
    public static function cancelPR(int $prId): void
    {
        $db = App::getInstance()->db();

        $pr = $db->selectOne("SELECT * FROM `purchase_requisitions` WHERE `id` = ?", [$prId]);
        if (!$pr) {
            throw new \RuntimeException('طلب الشراء غير موجود');
        }
        if (!in_array($pr['status'], ['draft', 'submitted'], true)) {
            throw new \RuntimeException('لا يمكن إلغاء طلب الشراء في حالته الحالية');
        }

        $db->update('purchase_requisitions', [
            'status'     => 'cancelled',
            'updated_at' => date('Y-m-d H:i:s'),
        ], '`id` = ?', [$prId]);

        Logger::info("PR #{$prId} cancelled");
    }
}
