<?php

namespace App\Modules\Inventory\Services;

use App\Modules\Inventory\Models\Inventory;
use App\Modules\Inventory\Models\InventoryTransaction;
use App\Modules\Inventory\Models\GoodsReceiptItem;
use App\Modules\Inventory\Models\GoodsReceipt;
use App\Modules\Production\Contracts\ProductionServiceInterface;
use App\Modules\Procurement\Contracts\ProcurementServiceInterface;
use Illuminate\Support\Collection;

class TraceabilityService
{
    public function __construct(
        protected ProductionServiceInterface $productionService,
        protected ProcurementServiceInterface $procurementService
    ) {}

    /**
     * 逆向溯源：從成品批號往前追溯用到的所有原料與廠商
     * 
     * @param string $batchNumber 成品批號
     * @return array 樹狀結構資料
     */
    public function traceBackward(string $batchNumber): array
    {
        // 取得基本庫存資訊以作為根節點參考
        $baseInventory = Inventory::with(['product', 'warehouse'])
            ->where('batch_number', $batchNumber)
            ->first();

        // 定義根節點
        $rootNode = [
            'id' => 'batch_' . $batchNumber,
            'type' => 'target_batch',
            'label' => '查詢批號: ' . $batchNumber,
            'batch_number' => $batchNumber,
            'product_name' => $baseInventory?->product?->name,
            'spec' => $baseInventory?->product?->spec,
            'warehouse_name' => $baseInventory?->warehouse?->name,
            'children' => []
        ];

        // 1. 尋找這個批號是不是生產出來的成品 (Production Order Output)
        // 透過 ProductionService 獲取，以落實模組解耦
        $productionOrders = $this->productionService->getProductionOrdersByOutputBatch($batchNumber);

        foreach ($productionOrders as $po) {
            $poNode = [
                'id' => 'po_' . $po->id,
                'type' => 'production_order',
                'label' => '生產工單: ' . $po->code,
                'date' => $po->production_date instanceof \DateTimeInterface 
                    ? $po->production_date->format('Y-m-d') 
                    : $po->production_date,
                'quantity' => $po->output_quantity,
                'children' => []
            ];

            // 針對每一張工單，尋找它投料的原料批號
            foreach ($po->items as $item) {
                if (isset($item->inventory)) {
                    $materialNode = $this->buildMaterialBackwardNode($item->inventory, $item);
                    $poNode['children'][] = $materialNode;
                }
            }
            $rootNode['children'][] = $poNode;
        }

        // 2. 如果這批號是直接採購進來的 (Goods Receipt)
        // 或者是為了補足直接查詢原料批號的場景
        $inventories = Inventory::with(['product', 'warehouse'])
            ->where('batch_number', $batchNumber)
            ->get();

        foreach ($inventories as $inv) {
            // 尋找進貨單
            $grItems = GoodsReceiptItem::with(['goodsReceipt', 'product'])
                ->where('batch_number', $batchNumber)
                ->where('product_id', $inv->product_id)
                ->get();
            
            foreach ($grItems as $grItem) {
                $gr = $grItem->goodsReceipt;
                if ($gr) {
                    $grNode = [
                        'id' => 'gr_' . $gr->id . '_' . $inv->id,
                        'type' => 'goods_receipt',
                        'label' => '進貨單: ' . $gr->code,
                        'date' => $gr->received_date instanceof \DateTimeInterface 
                            ? $gr->received_date->format('Y-m-d') 
                            : $gr->received_date,
                        'vendor_id' => $gr->vendor_id,
                        'quantity' => $grItem->quantity,
                        'product_name' => $grItem->product?->name,
                        'children' => []
                    ];
                    
                    // 避免重複加入
                    $isDuplicate = false;
                    foreach ($rootNode['children'] as $child) {
                        if ($child['id'] === $grNode['id']) {
                            $isDuplicate = true;
                            break;
                        }
                    }
                    if (!$isDuplicate) {
                        $rootNode['children'][] = $grNode;
                    }
                }
            }
        }

        // 補充廠商名稱 (跨模組)
        $this->hydrateVendorNames($rootNode);

        return $rootNode;
    }

    /**
     * 建立原料的逆向溯源節點
     */
    private function buildMaterialBackwardNode(Inventory $inventory, $poItem = null): array
    {
        $node = [
            'id' => 'inv_' . $inventory->id,
            'type' => 'material_batch',
            'label' => '原料批號: ' . $inventory->batch_number,
            'product_name' => $inventory->product?->name,
            'spec' => $inventory->product?->spec,
            'batch_number' => $inventory->batch_number,
            'quantity' => $poItem ? $poItem->quantity_used : null,
            'warehouse_name' => $inventory->warehouse?->name,
            'children' => []
        ];

        // 繼續往下追溯該原料是怎麼來的 (進貨單)
        if ($inventory->batch_number) {
            $grItems = GoodsReceiptItem::with(['goodsReceipt', 'product'])
                ->where('batch_number', $inventory->batch_number)
                ->where('product_id', $inventory->product_id)
                ->get();
                
            foreach ($grItems as $grItem) {
                $gr = $grItem->goodsReceipt;
                if ($gr) {
                    $node['children'][] = [
                        'id' => 'gr_' . $gr->id,
                        'type' => 'goods_receipt',
                        'label' => '進貨單: ' . $gr->code,
                        'date' => $gr->received_date instanceof \DateTimeInterface 
                            ? $gr->received_date->format('Y-m-d') 
                            : $gr->received_date,
                        'vendor_id' => $gr->vendor_id,
                        'quantity' => $grItem->quantity,
                        'product_name' => $grItem->product?->name,
                        'children' => []
                    ];
                }
            }
        }

        return $node;
    }

    /**
     * 順向追蹤：從原料批號往後追查被用在哪些成品及去向
     * 
     * @param string $batchNumber 原料批號
     * @return array 樹狀結構資料
     */
    public function traceForward(string $batchNumber): array
    {
        $baseInventory = Inventory::with(['product', 'warehouse'])
            ->where('batch_number', $batchNumber)
            ->first();

        $rootNode = [
            'id' => 'batch_' . $batchNumber,
            'type' => 'source_batch',
            'label' => '查詢批號: ' . $batchNumber,
            'batch_number' => $batchNumber,
            'product_name' => $baseInventory?->product?->name,
            'spec' => $baseInventory?->product?->spec,
            'warehouse_name' => $baseInventory?->warehouse?->name,
            'children' => []
        ];

        // 1. 尋找這個批號被哪些工單使用了
        $inventories = Inventory::with(['product', 'warehouse'])->where('batch_number', $batchNumber)->get();
        
        foreach ($inventories as $inv) {
            // 透過 ProductionService 獲取，以落實模組解耦
            $poItems = $this->productionService->getProductionOrderItemsByInventoryId($inv->id, ['productionOrder']);
                
            foreach ($poItems as $item) {
                $po = $item->productionOrder;
                if ($po) {
                    $poNode = [
                        'id' => 'po_' . $po->id,
                        'type' => 'production_order',
                        'label' => '投入工單: ' . $po->code,
                        'date' => $po->production_date instanceof \DateTimeInterface 
                            ? $po->production_date->format('Y-m-d') 
                            : $po->production_date,
                        'quantity' => $item->quantity_used,
                        'children' => []
                    ];
                    
                    // 該工單產出的成品批號
                    if ($po->output_batch_number) {
                        $outputInventory = Inventory::with(['product', 'warehouse'])
                            ->where('batch_number', $po->output_batch_number)
                            ->first();

                        $outputNode = [
                            'id' => 'output_batch_' . $po->output_batch_number,
                            'type' => 'target_batch',
                            'label' => '產出成品: ' . $po->output_batch_number,
                            'batch_number' => $po->output_batch_number,
                            'quantity' => $po->output_quantity,
                            'product_name' => $outputInventory?->product?->name,
                            'spec' => $outputInventory?->product?->spec,
                            'warehouse_name' => $outputInventory?->warehouse?->name,
                            'children' => []
                        ];
                        
                        // 追蹤成品的出庫紀錄 (銷貨、領料等)
                        $outTransactions = InventoryTransaction::with(['reference', 'inventory.product'])
                            ->whereHas('inventory', function ($q) use ($po) {
                                $q->where('batch_number', $po->output_batch_number);
                            })
                            ->where('quantity', '<', 0) // 出庫
                            ->get();
                            
                        foreach ($outTransactions as $txn) {
                            $refType = class_basename($txn->reference_type);
                            $outputNode['children'][] = [
                                'id' => 'txn_' . $txn->id,
                                'type' => 'outbound_transaction',
                                'label' => '出庫單據: ' . $refType . ' #' . $txn->reference_id,
                                'date' => $txn->actual_time,
                                'quantity' => abs($txn->quantity),
                                'product_name' => $txn->inventory?->product?->name,
                                'children' => []
                            ];
                        }
                        
                        $poNode['children'][] = $outputNode;
                    }
                    
                    $rootNode['children'][] = $poNode;
                }
            }
        }
        
        // 2. 如果這個批號自己本身就有出庫紀錄 (不是被生產掉，而是直接被領走或賣掉)
        foreach ($inventories as $inv) {
            $outTransactions = InventoryTransaction::with(['reference', 'inventory.product'])
                ->where('inventory_id', $inv->id)
                ->where('quantity', '<', 0)
                ->get();
                
            foreach ($outTransactions as $txn) {
                // 如果是生產工單領料，上面已經處理過，這裡濾掉
                if ($txn->reference_type && str_contains($txn->reference_type, 'ProductionOrder')) {
                    continue;
                }
                
                $refType = $txn->reference_type ? class_basename($txn->reference_type) : '未知';
                $rootNode['children'][] = [
                    'id' => 'txn_direct_' . $txn->id,
                    'type' => 'outbound_transaction',
                    'label' => '直接出庫: ' . $refType . ' #' . $txn->reference_id,
                    'date' => $txn->actual_time,
                    'quantity' => abs($txn->quantity),
                    'product_name' => $txn->inventory?->product?->name,
                    'children' => []
                ];
            }
        }

        return $rootNode;
    }

    /**
     * 水和廠商名稱 (跨模組)
     */
    private function hydrateVendorNames(array &$node): void
    {
        $vendorIds = [];
        $this->collectVendorIds($node, $vendorIds);

        if (empty($vendorIds)) return;

        $vendors = $this->procurementService->getVendorsByIds(array_unique($vendorIds))->keyBy('id');

        $this->applyVendorNames($node, $vendors);
    }

    private function collectVendorIds(array $node, array &$ids): void
    {
        if (isset($node['vendor_id'])) {
            $ids[] = $node['vendor_id'];
        }
        if (!empty($node['children'])) {
            foreach ($node['children'] as $child) {
                $this->collectVendorIds($child, $ids);
            }
        }
    }

    private function applyVendorNames(array &$node, Collection $vendors): void
    {
        if (isset($node['vendor_id']) && $vendors->has($node['vendor_id'])) {
            $vendor = $vendors->get($node['vendor_id']);
            $node['label'] .= ' (廠商: ' . $vendor->name . ')';
        }
        if (!empty($node['children'])) {
            foreach ($node['children'] as &$child) {
                $this->applyVendorNames($child, $vendors);
            }
        }
    }
}
