<?php
declare(strict_types=1);

namespace App\Modules\Inventory\Services;

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

/**
 * Single entry point for ALL stock changes.
 * Every stock in/out MUST go through moveStock().
 */
final class StockService
{
    /**
     * Record a stock movement and update warehouse stock.
     *
     * @param array $data Keys:
     *   - item_id         (int, required)
     *   - warehouse_id    (int, required)
     *   - movement_type   (string, required)  e.g. purchase_in, sale_out, transfer_in, ...
     *   - direction        (string, required)  'in' or 'out'
     *   - quantity         (string, required)  positive decimal
     *   - unit_cost        (string|null)
     *   - batch_id         (int|null)
     *   - reference_type   (string|null)       e.g. 'purchase_orders', 'sales'
     *   - reference_id     (int|null)
     *   - transfer_id      (int|null)
     *   - notes            (string|null)
     *   - movement_date    (string|null)       defaults to today
     * @return int  The stock_movements.id
     * @throws \RuntimeException on validation/stock failure
     */
    public static function moveStock(array $data): int
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $itemId      = (int) ($data['item_id'] ?? 0);
        $warehouseId = (int) ($data['warehouse_id'] ?? 0);
        $moveType    = (string) ($data['movement_type'] ?? '');
        $direction   = (string) ($data['direction'] ?? '');
        $quantity    = (string) ($data['quantity'] ?? '0');
        $unitCost    = isset($data['unit_cost']) ? (string) $data['unit_cost'] : null;
        $batchId     = isset($data['batch_id']) ? (int) $data['batch_id'] : null;
        $refType     = $data['reference_type'] ?? null;
        $refId       = isset($data['reference_id']) ? (int) $data['reference_id'] : null;
        $transferId  = isset($data['transfer_id']) ? (int) $data['transfer_id'] : null;
        $notes       = $data['notes'] ?? null;
        $moveDate    = $data['movement_date'] ?? date('Y-m-d');

        // ── Validation ──
        if ($itemId <= 0) {
            throw new \RuntimeException('الصنف غير محدد');
        }
        if ($warehouseId <= 0) {
            throw new \RuntimeException('المخزن غير محدد');
        }
        if (!in_array($direction, ['in', 'out'], true)) {
            throw new \RuntimeException('اتجاه الحركة غير صالح');
        }
        if (bccomp($quantity, '0', 3) <= 0) {
            throw new \RuntimeException('الكمية يجب أن تكون أكبر من صفر');
        }

        // Verify item exists and is active
        $item = $db->selectOne(
            "SELECT `id`, `tracking_type`, `name_ar` FROM `inventory_items` WHERE `id` = ? AND `is_archived` = 0",
            [$itemId]
        );
        if (!$item) {
            throw new \RuntimeException('الصنف غير موجود');
        }

        // Verify warehouse exists and is active
        $warehouse = $db->selectOne(
            "SELECT `id` FROM `warehouses` WHERE `id` = ? AND `is_active` = 1 AND `is_archived` = 0",
            [$warehouseId]
        );
        if (!$warehouse) {
            throw new \RuntimeException('المخزن غير موجود أو غير نشط');
        }

        // For OUT movements, verify sufficient stock
        if ($direction === 'out') {
            $currentQty = self::getAvailableQuantity($itemId, $warehouseId);
            if (bccomp($currentQty, $quantity, 3) < 0) {
                throw new \RuntimeException(
                    'الرصيد غير كافٍ — المتاح: ' . $currentQty . ' المطلوب: ' . $quantity
                );
            }
        }

        $totalCost = null;
        if ($unitCost !== null) {
            $totalCost = bcmul($unitCost, $quantity, 2);
        }

        $db->beginTransaction();
        try {
            // Lock the stock row for concurrency safety
            $db->selectOne(
                "SELECT `id` FROM `item_warehouse_stock` WHERE `item_id` = ? AND `warehouse_id` = ? FOR UPDATE",
                [$itemId, $warehouseId]
            );

            // Insert movement record
            $movementId = $db->insert('stock_movements', [
                'warehouse_id'   => $warehouseId,
                'item_id'        => $itemId,
                'movement_type'  => $moveType,
                'direction'      => $direction,
                'quantity'       => $quantity,
                'unit_cost'      => $unitCost,
                'total_cost'     => $totalCost,
                'batch_id'       => $batchId,
                'reference_type' => $refType,
                'reference_id'   => $refId,
                'transfer_id'    => $transferId,
                'notes'          => $notes,
                'movement_date'  => $moveDate,
                'created_by'     => $employee ? (int) $employee->id : null,
                'created_at'     => date('Y-m-d H:i:s'),
            ]);

            // Upsert stock level
            self::upsertStockLevel($itemId, $warehouseId, $quantity, $direction);

            // If batch-tracked OUT, deduct from batch
            if ($direction === 'out' && $batchId) {
                self::deductBatch($batchId, $quantity);
            }

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

        // Fire event outside transaction
        EventBus::dispatch('inventory.stock_moved', [
            'movement_id'   => $movementId,
            'item_id'       => $itemId,
            'warehouse_id'  => $warehouseId,
            'direction'     => $direction,
            'quantity'       => $quantity,
            'movement_type' => $moveType,
        ]);

        // Check low stock after OUT
        if ($direction === 'out') {
            $newQty = self::getAvailableQuantity($itemId, $warehouseId);
            $minLevel = self::getMinLevel($itemId, $warehouseId);
            if ($minLevel !== null && bccomp($newQty, (string) $minLevel, 3) <= 0) {
                EventBus::dispatch('inventory.low_stock_alert', [
                    'item_id'      => $itemId,
                    'warehouse_id' => $warehouseId,
                    'quantity'     => $newQty,
                    'min_level'    => $minLevel,
                ]);
            }
        }

        Logger::info("Stock movement #{$movementId}: {$direction} {$quantity} of item #{$itemId} in warehouse #{$warehouseId} ({$moveType})");

        return $movementId;
    }

    /**
     * Get the available quantity for an item in a warehouse.
     */
    public static function getAvailableQuantity(int $itemId, int $warehouseId): string
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne(
            "SELECT `quantity` FROM `item_warehouse_stock` WHERE `item_id` = ? AND `warehouse_id` = ?",
            [$itemId, $warehouseId]
        );
        return (string) ($row['quantity'] ?? '0.000');
    }

    /**
     * Get total stock across all warehouses.
     */
    public static function getTotalStock(int $itemId): string
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne(
            "SELECT COALESCE(SUM(`quantity`), 0) as total FROM `item_warehouse_stock` WHERE `item_id` = ?",
            [$itemId]
        );
        return (string) ($row['total'] ?? '0.000');
    }

    /**
     * Get movement history for an item (optionally in a specific warehouse).
     */
    public static function getMovements(int $itemId, ?int $warehouseId = null, int $limit = 50): array
    {
        $db = App::getInstance()->db();
        $where = '`item_id` = ?';
        $params = [$itemId];
        if ($warehouseId) {
            $where .= ' AND `warehouse_id` = ?';
            $params[] = $warehouseId;
        }
        return $db->select(
            "SELECT * FROM `stock_movements` WHERE {$where} ORDER BY `created_at` DESC LIMIT {$limit}",
            $params
        );
    }

    // ── Private helpers ──

    private static function upsertStockLevel(int $itemId, int $warehouseId, string $quantity, string $direction): void
    {
        $db = App::getInstance()->db();
        $existing = $db->selectOne(
            "SELECT `id` FROM `item_warehouse_stock` WHERE `item_id` = ? AND `warehouse_id` = ?",
            [$itemId, $warehouseId]
        );

        if ($existing) {
            $op = $direction === 'in' ? '+' : '-';
            $db->statement(
                "UPDATE `item_warehouse_stock` SET `quantity` = `quantity` {$op} ?, `updated_at` = NOW() WHERE `item_id` = ? AND `warehouse_id` = ?",
                [$quantity, $itemId, $warehouseId]
            );
        } else {
            $qty = $direction === 'in' ? $quantity : '-' . $quantity;
            $db->insert('item_warehouse_stock', [
                'item_id'      => $itemId,
                'warehouse_id' => $warehouseId,
                'quantity'     => $qty,
                'updated_at'   => date('Y-m-d H:i:s'),
            ]);
        }
    }

    private static function deductBatch(int $batchId, string $quantity): void
    {
        $db = App::getInstance()->db();
        $db->statement(
            "UPDATE `item_batches` SET `quantity` = `quantity` - ? WHERE `id` = ?",
            [$quantity, $batchId]
        );
        // Mark depleted if quantity reaches zero
        $db->statement(
            "UPDATE `item_batches` SET `status` = 'depleted' WHERE `id` = ? AND `quantity` <= 0",
            [$batchId]
        );
    }

    private static function getMinLevel(int $itemId, int $warehouseId): ?string
    {
        $db = App::getInstance()->db();
        $row = $db->selectOne(
            "SELECT `min_level` FROM `item_warehouse_stock` WHERE `item_id` = ? AND `warehouse_id` = ?",
            [$itemId, $warehouseId]
        );
        return $row['min_level'] ?? null;
    }
}
