<?php

namespace App\Services;

use App\Models\Grn;
use App\Models\GrnItem;
use App\Models\StockLedger;
use App\Models\StockBalance;
use App\Models\WarehouseBin;
use App\Models\Item;
use App\Models\WorkOrder;
use App\Models\WoMaterialIssue;
use App\Models\WoReceipt;
use App\Models\BomItem;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class InventoryService
{
    /**
     * Process GRN as IGP (Inward Gate Pass) - Add items to warehouse
     */
    public function processIGP($grnId): bool
    {
        return DB::transaction(function () use ($grnId) {
            try {
                $grn = Grn::with(['items.purchaseOrderItem.item'])->findOrFail($grnId);

                if ($grn->status !== 'accepted') {
                    throw new \Exception('GRN must be in accepted status to process as IGP');
                }

                // Get receipt bin for the warehouse
                if (!$grn->warehouse_id) {
                    throw new \Exception('GRN must have a warehouse assigned');
                }

                $receiptBin = WarehouseBin::where('warehouse_id', $grn->warehouse_id)
                    ->where('type', 'receipt')
                    ->where('is_active', true)
                    ->first();

                if (!$receiptBin) {
                    throw new \Exception('Receipt bin not found for warehouse');
                }

                foreach ($grn->items as $grnItem) {
                    if ($grnItem->accepted_qty <= 0) {
                        continue; // Skip items with no accepted quantity
                    }

                    // Get unit cost from PO item
                    $unitCost = $grnItem->purchaseOrderItem->rate ?? 0;

                    // Create warehouse ledger entry (IGP)
                    StockLedger::create([
                        'txn_type' => 'igp',
                        'txn_id' => $grnId,
                        'item_id' => $grnItem->item_id,
                        'lot_no' => $grnItem->lot_no,
                        'bin_id' => $receiptBin->id,
                        'qty_in' => $grnItem->accepted_qty,
                        'qty_out' => 0,
                        'uom_id' => $grnItem->item->uom_id,
                        'unit_cost' => $unitCost,
                        'ref_no' => $grn->grn_number,
                        'occurred_at' => $grn->received_date,
                    ]);

                    // Update stock balance
                    $this->updateStockBalance(
                        $grnItem->item_id,
                        $grn->warehouse_id,
                        $receiptBin->id,
                        $grnItem->lot_no,
                        $grnItem->accepted_qty,
                        $unitCost,
                        $grnItem->item->uom_id
                    );
                }

                // Update GRN status to 'posted'
                $grn->update(['status' => 'posted']);

                Log::info("IGP processed for GRN {$grn->grn_number} - Items added to warehouse");
                return true;

            } catch (\Exception $e) {
                Log::error("Failed to process IGP for GRN {$grnId}: " . $e->getMessage());
                throw $e;
            }
        });
    }

    /**
     * Get warehouse ledger entries (IGP history)
     */
    public function getWarehouseLedger($filters = []): \Illuminate\Database\Eloquent\Collection
    {
        $query = StockLedger::with(['item', 'bin.warehouse'])
            ->orderBy('occurred_at', 'desc');

        // Apply filters
        if (isset($filters['item_id'])) {
            $query->where('item_id', $filters['item_id']);
        }

        if (isset($filters['warehouse_id'])) {
            $query->whereHas('bin', function($q) use ($filters) {
                $q->where('warehouse_id', $filters['warehouse_id']);
            });
        }

        if (isset($filters['txn_type'])) {
            $query->where('txn_type', $filters['txn_type']);
        }

        if (isset($filters['date_from'])) {
            $query->where('occurred_at', '>=', $filters['date_from']);
        }

        if (isset($filters['date_to'])) {
            $query->where('occurred_at', '<=', $filters['date_to']);
        }

        return $query->get();
    }

    /**
     * Get current stock balances
     */
    public function getStockBalances($filters = []): \Illuminate\Database\Eloquent\Collection
    {
        $query = StockBalance::with(['item', 'warehouse', 'bin'])
            ->where('on_hand_qty', '>', 0);

        // Apply filters
        if (isset($filters['item_id'])) {
            $query->where('item_id', $filters['item_id']);
        }

        if (isset($filters['warehouse_id'])) {
            $query->where('warehouse_id', $filters['warehouse_id']);
        }

        if (isset($filters['bin_id'])) {
            $query->where('bin_id', $filters['bin_id']);
        }

        return $query->get();
    }

    /**
     * Update stock balance with weighted average calculation
     */
    private function updateStockBalance($itemId, $warehouseId, $binId, $lotNo, $qtyChange, $unitCost, $uomId): void
    {
        $balance = StockBalance::where('item_id', $itemId)
            ->where('warehouse_id', $warehouseId)
            ->where('bin_id', $binId)
            ->where('lot_no', $lotNo)
            ->lockForUpdate()
            ->first();

        if (!$balance) {
            // Create new balance record
            StockBalance::create([
                'item_id' => $itemId,
                'warehouse_id' => $warehouseId,
                'bin_id' => $binId,
                'lot_no' => $lotNo,
                'on_hand_qty' => max(0, $qtyChange),
                'reserved_qty' => 0,
                'unit_cost_avg' => $unitCost,
            ]);
        } else {
            // Update existing balance with weighted average
            $currentQty = (float) $balance->on_hand_qty;
            $currentCost = (float) $balance->unit_cost_avg;
            $newQty = $currentQty + $qtyChange;

            if ($newQty < 0) {
                throw new \Exception("Stock balance cannot be negative. Current: {$currentQty}, Change: {$qtyChange}");
            }

            // Calculate weighted average cost
            $newCost = $currentCost;
            if ($qtyChange > 0 && $unitCost > 0) {
                if ($currentQty > 0) {
                    $newCost = (($currentQty * $currentCost) + ($qtyChange * $unitCost)) / ($currentQty + $qtyChange);
                } else {
                    $newCost = $unitCost;
                }
            }

            $balance->update([
                'on_hand_qty' => $newQty,
                'unit_cost_avg' => $newCost,
            ]);
        }
    }

    /**
     * Get on-hand quantity for item in specific bin and lot
     */
    public function getOnHand($itemId, $binId, $lotNo = null): float
    {
        $balance = StockBalance::where('item_id', $itemId)
            ->where('bin_id', $binId)
            ->where('lot_no', $lotNo)
            ->first();

        return $balance ? (float) $balance->on_hand_qty : 0.0;
    }

    /**
     * Issue materials to Work Order
     */
    public function issueToWO($woId, $itemId, $lotNo, $binId, $qty): bool
    {
        return DB::transaction(function () use ($woId, $itemId, $lotNo, $binId, $qty) {
            try {
                $workOrder = WorkOrder::findOrFail($woId);
                $item = Item::findOrFail($itemId);
                $bin = WarehouseBin::findOrFail($binId);

                // Check if work order can issue materials
                if (!$workOrder->canIssueMaterials()) {
                    throw new \Exception('Work Order is not in a state that allows material issues');
                }

                // Check stock availability
                $availableQty = $this->getOnHand($itemId, $binId, $lotNo);
                if ($availableQty < $qty) {
                    throw new \Exception("Insufficient stock. Available: {$availableQty}, Required: {$qty}");
                }

                // Get current stock balance for cost calculation
                $balance = StockBalance::where('item_id', $itemId)
                    ->where('bin_id', $binId)
                    ->where('lot_no', $lotNo)
                    ->lockForUpdate()
                    ->first();

                if (!$balance) {
                    throw new \Exception('Stock balance not found for the specified bin and lot');
                }

                $unitCost = (float) $balance->unit_cost_avg;

                // Create material issue record
                WoMaterialIssue::create([
                    'work_order_id' => $woId,
                    'item_id' => $itemId,
                    'lot_no' => $lotNo,
                    'bin_id' => $binId,
                    'qty_issued' => $qty,
                    'occurred_at' => now(),
                ]);

                // Create warehouse ledger entry (material issue)
                StockLedger::create([
                    'txn_type' => 'production_issue',
                    'txn_id' => $woId,
                    'item_id' => $itemId,
                    'lot_no' => $lotNo,
                    'bin_id' => $binId,
                    'qty_in' => 0,
                    'qty_out' => $qty,
                    'uom_id' => $item->uom_id,
                    'unit_cost' => $unitCost,
                    'ref_no' => $workOrder->batch_no ?? "WO-{$woId}",
                    'occurred_at' => now(),
                ]);

                // Update stock balance
                $this->updateStockBalance(
                    $itemId,
                    $bin->warehouse_id,
                    $binId,
                    $lotNo,
                    -$qty, // Negative for issue
                    $unitCost,
                    $item->uom_id
                );

                Log::info("Material issued to WO {$woId}: {$qty} x {$item->name} from bin {$bin->name}");
                return true;

            } catch (\Exception $e) {
                Log::error("Failed to issue material to WO {$woId}: " . $e->getMessage());
                throw $e;
            }
        });
    }

    /**
     * Receive finished goods from Work Order
     */
    public function receiveFromWO($woId, $itemId, $lotNo, $binId, $qty, $unitCostOptional = null): bool
    {
        return DB::transaction(function () use ($woId, $itemId, $lotNo, $binId, $qty, $unitCostOptional) {
            try {
                $workOrder = WorkOrder::findOrFail($woId);
                $item = Item::findOrFail($itemId);
                $bin = WarehouseBin::findOrFail($binId);

                // Check if work order can receive
                if (!$workOrder->canReceive()) {
                    throw new \Exception('Work Order is not in a state that allows receipts');
                }

                // Calculate unit cost if not provided
                $unitCost = $unitCostOptional;
                if ($unitCost === null) {
                    $unitCost = $this->calculateWOUnitCost($woId, $qty);
                }

                // Create receipt record
                WoReceipt::create([
                    'work_order_id' => $woId,
                    'item_id' => $itemId,
                    'lot_no' => $lotNo,
                    'bin_id' => $binId,
                    'qty_received' => $qty,
                    'unit_cost_calc' => $unitCost,
                    'occurred_at' => now(),
                ]);

                // Create warehouse ledger entry (WO receipt)
                StockLedger::create([
                    'txn_type' => 'production_receipt',
                    'txn_id' => $woId,
                    'item_id' => $itemId,
                    'lot_no' => $lotNo,
                    'bin_id' => $binId,
                    'qty_in' => $qty,
                    'qty_out' => 0,
                    'uom_id' => $item->uom_id,
                    'unit_cost' => $unitCost,
                    'ref_no' => $workOrder->batch_no ?? "WO-{$woId}",
                    'occurred_at' => now(),
                ]);

                // Update stock balance
                $this->updateStockBalance(
                    $itemId,
                    $bin->warehouse_id,
                    $binId,
                    $lotNo,
                    $qty, // Positive for receipt
                    $unitCost,
                    $item->uom_id
                );

                Log::info("FG received from WO {$woId}: {$qty} x {$item->name} to bin {$bin->name}");
                return true;

            } catch (\Exception $e) {
                Log::error("Failed to receive from WO {$woId}: " . $e->getMessage());
                throw $e;
            }
        });
    }

    /**
     * Calculate unit cost for WO receipt based on material costs
     */
    private function calculateWOUnitCost($woId, $qtyReceived): float
    {
        $workOrder = WorkOrder::with(['materialIssues.item'])->findOrFail($woId);

        $totalMaterialCost = 0;
        foreach ($workOrder->materialIssues as $issue) {
            // Get cost from stock balance for this specific issue
            $balance = StockBalance::where('item_id', $issue->item_id)
                ->where('bin_id', $issue->bin_id)
                ->where('lot_no', $issue->lot_no)
                ->first();

            $unitCost = $balance ? (float) $balance->unit_cost_avg : 0;
            $totalMaterialCost += $issue->qty_issued * $unitCost;
        }

        // Simple weighted average: total material cost / quantity received
        return $qtyReceived > 0 ? $totalMaterialCost / $qtyReceived : 0;
    }

    /**
     * Get BOM requirements for a work order
     */
        public function getBOMRequirements($woId): array
    {
        $workOrder = WorkOrder::with(['bom.bomItems.component'])->findOrFail($woId);

        if (!$workOrder->bom) {
            return [];
        }

        $requirements = [];
        foreach ($workOrder->bom->bomItems as $bomItem) {
            $requiredQty = $workOrder->qty_planned * $bomItem->qty_per;
            $availableQty = $this->getOnHand($bomItem->component_item_id, $workOrder->warehouse_id);

            $requirements[] = [
                'item_id' => $bomItem->component_item_id,
                'item_name' => $bomItem->component->name,
                'required_qty' => $requiredQty,
                'available_qty' => $availableQty,
                'shortage_qty' => max(0, $requiredQty - $availableQty),
                'can_issue' => $availableQty >= $requiredQty
            ];
        }

        return $requirements;
    }
}
