<?php
declare(strict_types=1);

namespace App\Modules\Inventory\Services;

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

/**
 * Service for purchase order lifecycle management.
 */
final class PurchaseOrderService
{
    /**
     * Create a new purchase order with its items.
     *
     * @param array $header  Keys: supplier_id, warehouse_id, expected_delivery_date, notes
     * @param array $items   Each: ['item_id' => int, 'quantity_ordered' => string, 'unit_price' => string, 'notes' => ?string]
     * @return int PO ID
     */
    public static function createPO(array $header, array $items): int
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

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

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

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

        $poNumber = InventoryNumberGenerator::nextPONumber();

        // Calculate totals
        $totalAmount = '0.00';
        foreach ($items as $item) {
            $qty   = (string) ($item['quantity_ordered'] ?? '0');
            $price = (string) ($item['unit_price'] ?? '0');
            $lineTotal = bcmul($qty, $price, 2);
            $totalAmount = bcadd($totalAmount, $lineTotal, 2);
        }

        $taxAmount  = bcmul($totalAmount, '0.14', 2);
        $grandTotal = bcadd($totalAmount, $taxAmount, 2);

        $db->beginTransaction();
        try {
            $poId = $db->insert('purchase_orders', [
                'po_number'          => $poNumber,
                'supplier_id'        => $supplierId,
                'warehouse_id'       => $warehouseId,
                'status'             => 'draft',
                'order_date'         => date('Y-m-d'),
                'expected_delivery'  => $header['expected_delivery_date'] ?? null,
                'subtotal'           => $totalAmount,
                'tax_amount'         => $taxAmount,
                'discount_amount'    => '0.00',
                'total_amount'       => $grandTotal,
                'submitted_by'       => $employee ? (int) $employee->id : null,
                'notes'              => $header['notes'] ?? 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_ordered'] ?? '0');
                $price     = (string) ($item['unit_price'] ?? '0');
                $lineTotal = bcmul($qty, $price, 2);

                $db->insert('purchase_order_items', [
                    'purchase_order_id' => $poId,
                    'item_id'           => (int) $item['item_id'],
                    'quantity_ordered'   => $qty,
                    'quantity_received'  => '0',
                    'unit_cost'         => $price,
                    'line_total'        => $lineTotal,
                    'notes'             => $item['notes'] ?? null,
                ]);
            }

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

        Logger::info("PO #{$poId} ({$poNumber}) created for supplier #{$supplierId}, grand total: {$grandTotal}");

        return $poId;
    }

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

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

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

        Logger::info("PO #{$poId} submitted for approval");
    }

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

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

        $db->update('purchase_orders', [
            '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` = ?', [$poId]);

        Logger::info("PO #{$poId} approved");
    }

    /**
     * Receive items against an approved/partially_received PO.
     *
     * @param int   $poId
     * @param array $receivedItems  Each: ['po_item_id' => int, 'quantity_received' => string, 'batch_number' => ?string, 'expiry_date' => ?string]
     */
    public static function receivePO(int $poId, array $receivedItems): void
    {
        $db = App::getInstance()->db();

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

        $warehouseId = (int) $po['warehouse_id'];

        $db->beginTransaction();
        try {
            foreach ($receivedItems as $received) {
                $poItemId = (int) ($received['po_item_id'] ?? 0);
                $qtyReceived = (string) ($received['quantity_received'] ?? '0');

                if ($poItemId <= 0 || bccomp($qtyReceived, '0', 3) <= 0) {
                    continue;
                }

                $poItem = $db->selectOne(
                    "SELECT poi.*, i.`tracking_type`
                     FROM `purchase_order_items` poi
                     JOIN `inventory_items` i ON i.`id` = poi.`item_id`
                     WHERE poi.`id` = ? AND poi.`purchase_order_id` = ?",
                    [$poItemId, $poId]
                );
                if (!$poItem) {
                    continue;
                }

                // Update quantity_received on the PO item
                $newReceived = bcadd((string) $poItem['quantity_received'], $qtyReceived, 3);
                $db->update('purchase_order_items', [
                    'quantity_received' => $newReceived,
                ], '`id` = ?', [$poItemId]);

                // Create stock movement via StockService
                $batchId = null;
                if ($poItem['tracking_type'] === 'expiry' && !empty($received['batch_number']) && !empty($received['expiry_date'])) {
                    $batchId = BatchService::createBatch([
                        'item_id'       => (int) $poItem['item_id'],
                        'warehouse_id'  => $warehouseId,
                        'batch_number'  => $received['batch_number'],
                        'expiry_date'   => $received['expiry_date'],
                        'quantity'      => $qtyReceived,
                        'unit_cost'     => (string) $poItem['unit_cost'],
                        'received_date' => date('Y-m-d'),
                    ]);
                }

                StockService::moveStock([
                    'item_id'        => (int) $poItem['item_id'],
                    'warehouse_id'   => $warehouseId,
                    'movement_type'  => 'purchase_in',
                    'direction'      => 'in',
                    'quantity'       => $qtyReceived,
                    'unit_cost'      => (string) $poItem['unit_cost'],
                    'batch_id'       => $batchId,
                    'reference_type' => 'purchase_orders',
                    'reference_id'   => $poId,
                    'notes'          => 'استلام أمر شراء — ' . $po['po_number'],
                ]);
            }

            // Determine new PO status
            $allItems = $db->select(
                "SELECT `quantity_ordered`, `quantity_received` FROM `purchase_order_items` WHERE `purchase_order_id` = ?",
                [$poId]
            );

            $allFullyReceived = true;
            foreach ($allItems as $item) {
                if (bccomp((string) $item['quantity_received'], (string) $item['quantity_ordered'], 3) < 0) {
                    $allFullyReceived = false;
                    break;
                }
            }

            $newStatus = $allFullyReceived ? 'received' : 'partially_received';
            $db->update('purchase_orders', [
                'status'     => $newStatus,
                'updated_at' => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$poId]);

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

        EventBus::dispatch('inventory.po_received', [
            'po_id'        => $poId,
            'warehouse_id' => $warehouseId,
            'status'       => $newStatus,
        ]);

        Logger::info("PO #{$poId} received — new status: {$newStatus}");
    }

    /**
     * Cancel a PO (only from draft or submitted).
     */
    public static function cancelPO(int $poId): void
    {
        $db = App::getInstance()->db();

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

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

        Logger::info("PO #{$poId} cancelled");
    }
}
