<?php
declare(strict_types=1);

namespace App\Modules\Procurement\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Inventory\Services\StockService;
use App\Modules\Inventory\Services\BatchService;

final class GoodsReceivingService
{
    /**
     * Create a GRN from 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, 'notes' => ?string]
     * @param array $header        Optional: ['received_date' => ?string, 'notes' => ?string]
     * @return int GRN ID
     */
    public static function createGRN(int $poId, array $receivedItems, array $header = []): int
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $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('أمر الشراء ليس في حالة تسمح بالاستلام');
        }

        if (empty($receivedItems)) {
            throw new \RuntimeException('يجب تحديد أصناف مستلمة');
        }

        $grnNumber   = ProcurementNumberGenerator::nextGRNNumber();
        $warehouseId = (int) $po['warehouse_id'];
        $supplierId  = (int) $po['supplier_id'];

        $totalReceivedValue = '0.00';

        $db->beginTransaction();
        try {
            $grnId = $db->insert('goods_received_notes', [
                'grn_number'           => $grnNumber,
                'purchase_order_id'    => $poId,
                'supplier_id'          => $supplierId,
                'warehouse_id'         => $warehouseId,
                'received_by'          => $employee ? (int) $employee->id : null,
                'received_date'        => $header['received_date'] ?? date('Y-m-d'),
                'status'               => 'draft',
                'total_received_value' => '0.00',
                'total_accepted_value' => '0.00',
                'notes'                => $header['notes'] ?? null,
                'branch_id'            => $po['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 ($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 * FROM `purchase_order_items` WHERE `id` = ? AND `purchase_order_id` = ?",
                    [$poItemId, $poId]
                );
                if (!$poItem) {
                    continue;
                }

                $unitCost  = (string) $poItem['unit_cost'];
                $lineTotal = bcmul($qtyReceived, $unitCost, 2);
                $totalReceivedValue = bcadd($totalReceivedValue, $lineTotal, 2);

                $db->insert('grn_items', [
                    'grn_id'            => $grnId,
                    'po_item_id'        => $poItemId,
                    'item_id'           => (int) $poItem['item_id'],
                    'quantity_received' => $qtyReceived,
                    'quantity_accepted' => '0.000',
                    'quantity_rejected' => '0.000',
                    'batch_number'      => $received['batch_number'] ?? null,
                    'expiry_date'       => !empty($received['expiry_date']) ? $received['expiry_date'] : null,
                    'unit_cost'         => $unitCost,
                    'line_total'        => $lineTotal,
                    'notes'             => $received['notes'] ?? null,
                ]);
            }

            // Update GRN total
            $db->update('goods_received_notes', [
                'total_received_value' => $totalReceivedValue,
                'status'               => 'inspecting',
                'updated_at'           => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$grnId]);

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

        Logger::info("GRN #{$grnId} ({$grnNumber}) created for PO #{$poId}, total: {$totalReceivedValue}");
        return $grnId;
    }

    /**
     * Inspect/accept a GRN — sets accepted/rejected quantities and moves stock.
     *
     * @param int   $grnId
     * @param array $inspectedItems Each: ['grn_item_id' => int, 'quantity_accepted' => string, 'quantity_rejected' => string, 'rejection_reason' => ?string]
     * @param string|null $inspectionNotes
     */
    public static function inspectGRN(int $grnId, array $inspectedItems, ?string $inspectionNotes = null): void
    {
        $db = App::getInstance()->db();

        $grn = $db->selectOne("SELECT * FROM `goods_received_notes` WHERE `id` = ?", [$grnId]);
        if (!$grn) {
            throw new \RuntimeException('إذن الاستلام غير موجود');
        }
        if (!in_array($grn['status'], ['draft', 'inspecting'], true)) {
            throw new \RuntimeException('إذن الاستلام ليس في حالة تسمح بالفحص');
        }

        $warehouseId = (int) $grn['warehouse_id'];
        $poId        = (int) $grn['purchase_order_id'];
        $totalAcceptedValue = '0.00';
        $hasRejections = false;

        $db->beginTransaction();
        try {
            foreach ($inspectedItems as $inspected) {
                $grnItemId    = (int) ($inspected['grn_item_id'] ?? 0);
                $qtyAccepted  = (string) ($inspected['quantity_accepted'] ?? '0');
                $qtyRejected  = (string) ($inspected['quantity_rejected'] ?? '0');

                if ($grnItemId <= 0) {
                    continue;
                }

                $grnItem = $db->selectOne(
                    "SELECT gi.*, i.`tracking_type`
                     FROM `grn_items` gi
                     JOIN `inventory_items` i ON i.`id` = gi.`item_id`
                     WHERE gi.`id` = ? AND gi.`grn_id` = ?",
                    [$grnItemId, $grnId]
                );
                if (!$grnItem) {
                    continue;
                }

                // Update GRN item
                $db->update('grn_items', [
                    'quantity_accepted' => $qtyAccepted,
                    'quantity_rejected' => $qtyRejected,
                    'rejection_reason'  => $inspected['rejection_reason'] ?? null,
                ], '`id` = ?', [$grnItemId]);

                if (bccomp($qtyRejected, '0', 3) > 0) {
                    $hasRejections = true;
                }

                // Move accepted stock
                if (bccomp($qtyAccepted, '0', 3) > 0) {
                    $acceptedValue = bcmul($qtyAccepted, (string) $grnItem['unit_cost'], 2);
                    $totalAcceptedValue = bcadd($totalAcceptedValue, $acceptedValue, 2);

                    // Create batch if tracking type requires it
                    $batchId = null;
                    if ($grnItem['tracking_type'] === 'expiry' && !empty($grnItem['batch_number']) && !empty($grnItem['expiry_date'])) {
                        $batchId = BatchService::createBatch([
                            'item_id'       => (int) $grnItem['item_id'],
                            'warehouse_id'  => $warehouseId,
                            'batch_number'  => $grnItem['batch_number'],
                            'expiry_date'   => $grnItem['expiry_date'],
                            'quantity'      => $qtyAccepted,
                            'unit_cost'     => (string) $grnItem['unit_cost'],
                            'received_date' => date('Y-m-d'),
                        ]);
                    }

                    StockService::moveStock([
                        'item_id'        => (int) $grnItem['item_id'],
                        'warehouse_id'   => $warehouseId,
                        'movement_type'  => 'purchase_in',
                        'direction'      => 'in',
                        'quantity'       => $qtyAccepted,
                        'unit_cost'      => (string) $grnItem['unit_cost'],
                        'batch_id'       => $batchId,
                        'reference_type' => 'goods_received_notes',
                        'reference_id'   => $grnId,
                        'notes'          => 'استلام بضاعة — ' . $grn['grn_number'],
                    ]);

                    // Update PO item received quantity
                    if (!empty($grnItem['po_item_id'])) {
                        $poItem = $db->selectOne(
                            "SELECT * FROM `purchase_order_items` WHERE `id` = ?",
                            [(int) $grnItem['po_item_id']]
                        );
                        if ($poItem) {
                            $newReceived = bcadd((string) $poItem['quantity_received'], $qtyAccepted, 3);
                            $db->update('purchase_order_items', [
                                'quantity_received' => $newReceived,
                            ], '`id` = ?', [(int) $grnItem['po_item_id']]);
                        }
                    }
                }
            }

            // Determine GRN status
            $newStatus = $hasRejections ? 'partial_accept' : 'accepted';

            $db->update('goods_received_notes', [
                'status'               => $newStatus,
                'total_accepted_value' => $totalAcceptedValue,
                'inspection_notes'     => $inspectionNotes,
                'updated_at'           => date('Y-m-d H:i:s'),
            ], '`id` = ?', [$grnId]);

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

            $allFullyReceived = true;
            $anyReceived = false;
            foreach ($allPOItems as $poi) {
                if (bccomp((string) $poi['quantity_received'], '0', 3) > 0) {
                    $anyReceived = true;
                }
                if (bccomp((string) $poi['quantity_received'], (string) $poi['quantity_ordered'], 3) < 0) {
                    $allFullyReceived = false;
                }
            }

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

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

        EventBus::dispatch('procurement.grn_completed', [
            'grn_id'       => $grnId,
            'po_id'        => $poId,
            'supplier_id'  => (int) $grn['supplier_id'],
            'warehouse_id' => $warehouseId,
            'status'       => $newStatus,
        ]);

        // Also fire legacy event for backward compatibility
        EventBus::dispatch('inventory.po_received', [
            'po_id'        => $poId,
            'warehouse_id' => $warehouseId,
            'status'       => $poStatus,
        ]);

        Logger::info("GRN #{$grnId} inspected — status: {$newStatus}, accepted value: {$totalAcceptedValue}");
    }

    /**
     * Cancel a draft/inspecting GRN.
     */
    public static function cancelGRN(int $grnId): void
    {
        $db = App::getInstance()->db();

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

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

        Logger::info("GRN #{$grnId} cancelled");
    }
}
