<?php

namespace App\Modules\Inventory\Services;

use App\Modules\Inventory\Models\GoodsReceipt;
use App\Modules\Inventory\Models\GoodsReceiptItem;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use App\Modules\Procurement\Contracts\ProcurementServiceInterface;
use Illuminate\Support\Facades\DB;
use App\Modules\Inventory\Events\GoodsReceiptApprovedEvent;
use Illuminate\Support\Facades\Log;

class GoodsReceiptService implements \App\Modules\Inventory\Contracts\GoodsReceiptServiceInterface
{
    protected $inventoryService;
    protected $procurementService;

    public function __construct(
        InventoryServiceInterface $inventoryService,
        ProcurementServiceInterface $procurementService
    ) {
        $this->inventoryService = $inventoryService;
        $this->procurementService = $procurementService;
    }

    /**
     * Store a new Goods Receipt (Draft state).
     *
     * @param array $data
     * @return GoodsReceipt
     * @throws \Exception
     */
    public function store(array $data)
    {
        return DB::transaction(function () use ($data) {
            // 1. Generate Code
            $data['code'] = $this->generateCode($data['received_date']);
            $data['user_id'] = auth()->id();
            $data['status'] = GoodsReceipt::STATUS_DRAFT; // 預設草稿

            // 2. 靜默建立以抑制自動日誌（後續手動發送含品項明細的日誌）
            $goodsReceipt = new GoodsReceipt($data);
            $goodsReceipt->saveQuietly();

            // 3. 建立品項並收集 items_diff
            $diff = ['added' => [], 'removed' => [], 'updated' => []];
            $productIds = collect($data['items'])->pluck('product_id')->unique()->toArray();
            $products = $this->inventoryService->getProductsByIds($productIds)->keyBy('id');

            $itemsToInsert = [];
            foreach ($data['items'] as $itemData) {
                // 非標準類型：使用手動輸入的小計；標準類型：自動計算
                $totalAmount = !empty($itemData['subtotal']) && $data['type'] !== 'standard'
                    ? (float) $itemData['subtotal']
                    : $itemData['quantity_received'] * $itemData['unit_price'];

                $itemsToInsert[] = [
                    'goods_receipt_id' => $goodsReceipt->id,
                    'product_id' => $itemData['product_id'],
                    'purchase_order_item_id' => $itemData['purchase_order_item_id'] ?? null,
                    'quantity_received' => $itemData['quantity_received'],
                    'unit_price' => $itemData['unit_price'],
                    'total_amount' => $totalAmount,
                    'batch_number' => $itemData['batch_number'] ?? null,
                    'expiry_date' => $itemData['expiry_date'] ?? null,
                    'created_at' => now(),
                    'updated_at' => now(),
                ];

                $product = $products->get($itemData['product_id']);
                $diff['added'][] = [
                    'product_name' => $product?->name ?? '未知商品',
                    'new' => [
                        'quantity_received' => (float)$itemData['quantity_received'],
                        'unit_price' => (float)$itemData['unit_price'],
                        'total_amount' => (float)$totalAmount,
                    ]
                ];
            }

            if (!empty($itemsToInsert)) {
                GoodsReceiptItem::insert($itemsToInsert);
            }

            // 4. 手動發送高品質日誌（包含品項明細）
            activity()
                ->performedOn($goodsReceipt)
                ->causedBy(auth()->user())
                ->event('created')
                ->withProperties([
                    'items_diff' => $diff,
                    'attributes' => [
                        'gr_number' => $goodsReceipt->code,
                        'type' => $goodsReceipt->type,
                        'warehouse_id' => $goodsReceipt->warehouse_id,
                        'vendor_id' => $goodsReceipt->vendor_id,
                        'purchase_order_id' => $goodsReceipt->purchase_order_id,
                        'received_date' => $goodsReceipt->received_date,
                        'status' => $goodsReceipt->status,
                        'remarks' => $goodsReceipt->remarks,
                        'user_id' => $goodsReceipt->user_id,
                    ]
                ])
                ->log('created');

            return $goodsReceipt;
        });
    }

    public function update(GoodsReceipt $goodsReceipt, array $data)
    {
        if (!in_array($goodsReceipt->status, [GoodsReceipt::STATUS_DRAFT, GoodsReceipt::STATUS_REJECTED])) {
            throw new \Exception('只有草稿或被退回的進貨單可以修改。');
        }

        return DB::transaction(function () use ($goodsReceipt, $data) {
            $goodsReceipt->fill([
                'vendor_id' => $data['vendor_id'] ?? $goodsReceipt->vendor_id,
                'received_date' => $data['received_date'] ?? $goodsReceipt->received_date,
                'remarks' => $data['remarks'] ?? $goodsReceipt->remarks,
            ]);

            $dirty = $goodsReceipt->getDirty();
            $oldAttributes = [];
            $newAttributes = [];

            foreach ($dirty as $key => $value) {
                $oldAttributes[$key] = $goodsReceipt->getOriginal($key);
                $newAttributes[$key] = $value;
            }

            // 儲存但不觸發事件，以避免重複記錄
            $goodsReceipt->saveQuietly();

            // 捕捉包含商品名稱的舊項目以進行比對
            $oldItemsCollection = $goodsReceipt->items()->get();
            $oldProductIds = $oldItemsCollection->pluck('product_id')->unique()->toArray();
            $oldProducts = $this->inventoryService->getProductsByIds($oldProductIds)->keyBy('id');

            $oldItems = $oldItemsCollection->map(function($item) use ($oldProducts) {
                $product = $oldProducts->get($item->product_id);
                return [
                    'id' => $item->id,
                    'product_id' => $item->product_id,
                    'product_name' => $product?->name ?? 'Unknown',
                    'quantity_received' => (float) $item->quantity_received,
                    'unit_price' => (float) $item->unit_price,
                    'total_amount' => (float) $item->total_amount,
                ];
            })->keyBy('product_id');

            if (isset($data['items'])) {
                $goodsReceipt->items()->delete();

                $itemsToInsert = [];
                foreach ($data['items'] as $itemData) {
                    // 非標準類型：使用手動輸入的小計；標準類型：自動計算
                    $totalAmount = !empty($itemData['subtotal']) && $goodsReceipt->type !== 'standard'
                        ? (float) $itemData['subtotal']
                        : $itemData['quantity_received'] * $itemData['unit_price'];

                    $itemsToInsert[] = [
                        'goods_receipt_id' => $goodsReceipt->id,
                        'product_id' => $itemData['product_id'],
                        'purchase_order_item_id' => $itemData['purchase_order_item_id'] ?? null,
                        'quantity_received' => $itemData['quantity_received'],
                        'unit_price' => $itemData['unit_price'],
                        'total_amount' => $totalAmount,
                        'batch_number' => $itemData['batch_number'] ?? null,
                        'expiry_date' => $itemData['expiry_date'] ?? null,
                        'created_at' => now(),
                        'updated_at' => now(),
                    ];
                }

                if (!empty($itemsToInsert)) {
                    GoodsReceiptItem::insert($itemsToInsert);
                }
            }

            // 計算項目差異
            $itemDiffs = [
                'added' => [],
                'removed' => [],
                'updated' => [],
            ];

            $newItemsCollection = $goodsReceipt->items()->get();
            $newProductIds = $newItemsCollection->pluck('product_id')->unique()->toArray();
            $newProducts = $this->inventoryService->getProductsByIds($newProductIds)->keyBy('id');

            $newItemsFormatted = $newItemsCollection->map(function($item) use ($newProducts) {
                $product = $newProducts->get($item->product_id);
                return [
                    'product_id' => $item->product_id,
                    'product_name' => $product?->name ?? 'Unknown',
                    'quantity_received' => (float) $item->quantity_received,
                    'unit_price' => (float) $item->unit_price,
                    'total_amount' => (float) $item->total_amount,
                ];
            })->keyBy('product_id');

            foreach ($oldItems as $productId => $oldItem) {
                if (!$newItemsFormatted->has($productId)) {
                    $itemDiffs['removed'][] = $oldItem;
                }
            }

            foreach ($newItemsFormatted as $productId => $newItem) {
                if (!$oldItems->has($productId)) {
                    $itemDiffs['added'][] = $newItem;
                } else {
                    $oldItem = $oldItems[$productId];
                    if (
                        $oldItem['quantity_received'] != $newItem['quantity_received'] || 
                        $oldItem['unit_price'] != $newItem['unit_price'] ||
                        $oldItem['total_amount'] != $newItem['total_amount']
                    ) {
                        $itemDiffs['updated'][] = [
                            'product_name' => $newItem['product_name'],
                            'old' => [
                                'quantity_received' => $oldItem['quantity_received'],
                                'unit_price' => $oldItem['unit_price'],
                                'total_amount' => $oldItem['total_amount'],
                            ],
                            'new' => [
                                'quantity_received' => $newItem['quantity_received'],
                                'unit_price' => $newItem['unit_price'],
                                'total_amount' => $newItem['total_amount'],
                            ]
                        ];
                    }
                }
            }

            // 如果有變更，手動觸發單一合併日誌
            if (!empty($newAttributes) || !empty($itemDiffs['added']) || !empty($itemDiffs['removed']) || !empty($itemDiffs['updated'])) {
                activity()
                    ->performedOn($goodsReceipt)
                    ->causedBy(auth()->user())
                    ->event('updated')
                    ->withProperties([
                        'attributes' => $newAttributes,
                        'old' => $oldAttributes,
                        'items_diff' => $itemDiffs,
                    ])
                    ->log('updated');
            }

            return $goodsReceipt->fresh('items');
        });
    }

    /**
     * Submit for audit (Confirm receipt by warehouse staff).
     * This will increase inventory and update PO.
     *
     * @param GoodsReceipt $goodsReceipt
     * @return GoodsReceipt
     * @throws \Exception
     */
    public function submit(GoodsReceipt $goodsReceipt)
    {
        return DB::transaction(function () use ($goodsReceipt) {
            // Pessimistic locking to prevent double submission
            $goodsReceipt = GoodsReceipt::lockForUpdate()->find($goodsReceipt->id);

            if (!in_array($goodsReceipt->status, [GoodsReceipt::STATUS_DRAFT, GoodsReceipt::STATUS_REJECTED])) {
                throw new \Exception('只有草稿或被退回的進貨單可以確認點收。');
            }

            $goodsReceipt->status = GoodsReceipt::STATUS_COMPLETED;
            $goodsReceipt->save();

            // Process Inventory and PO updates
            foreach ($goodsReceipt->items as $grItem) {
                // 1. Update Inventory
                $reason = match($goodsReceipt->type) {
                    'standard' => '採購進貨',
                    'miscellaneous' => '雜項入庫',
                    'other' => '其他入庫',
                    default => '進貨入庫',
                };

                $quantityToRecord = $grItem->quantity_received;
                
                // 單位換算邏輯：僅針對標準採購且有連結 PO Item 時
                if ($goodsReceipt->type === 'standard' && $grItem->purchase_order_item_id) {
                    $poItem = \App\Modules\Procurement\Models\PurchaseOrderItem::find($grItem->purchase_order_item_id);
                    $product = $this->inventoryService->getProduct($grItem->product_id);
                    
                    if ($poItem && $product && $poItem->unit_id && $product->large_unit_id && $poItem->unit_id == $product->large_unit_id) {
                        // 如果使用的是大單位，則換算為基本單位數量
                        $quantityToRecord = $grItem->quantity_received * ($product->conversion_rate ?: 1);
                        Log::info("Goods Receipt [{$goodsReceipt->code}] converted quantity for product [{$product->id}]: {$grItem->quantity_received} large unit -> {$quantityToRecord} base unit.");
                    }
                }

                $this->inventoryService->createInventoryRecord([
                    'warehouse_id' => $goodsReceipt->warehouse_id,
                    'product_id' => $grItem->product_id,
                    'quantity' => $quantityToRecord,
                    'unit_cost' => $grItem->unit_price,
                    'batch_number' => $grItem->batch_number,
                    'expiry_date' => $grItem->expiry_date,
                    'reason' => $reason,
                    'reference_type' => GoodsReceipt::class,
                    'reference_id' => $goodsReceipt->id,
                    'source_purchase_order_id' => $goodsReceipt->purchase_order_id,
                    'arrival_date' => $goodsReceipt->received_date,
                ]);

                // 2. Update PO if linked and type is standard
                if ($goodsReceipt->type === 'standard' && $goodsReceipt->purchase_order_id && $grItem->purchase_order_item_id) {
                    // 更新採購單的實收數量 (維持原始單位數量，以便與採購數量比較)
                    $this->procurementService->updateReceivedQuantity(
                        $grItem->purchase_order_item_id,
                        $grItem->quantity_received
                    );
                }
            }

            // Fire event to let Finance module create AP
            event(new GoodsReceiptApprovedEvent($goodsReceipt->id));

            return $goodsReceipt;
        });
    }


    private function generateCode(string $date): string
    {
        // 使用 Cache Lock 防止併發時產生重複單號
        $lock = \Illuminate\Support\Facades\Cache::lock('gr_code_generation', 10);

        if (!$lock->get()) {
            throw new \Exception('系統忙碌中，進貨單號生成失敗，請稍後再試');
        }

        try {
            // Format: GR-YYYYMMDD-NN
            $prefix = 'GR-' . date('Ymd', strtotime($date)) . '-';
            
            $last = GoodsReceipt::where('code', 'like', $prefix . '%')
                ->orderBy('id', 'desc')
                ->first();

            if ($last) {
                $seq = intval(substr($last->code, -2)) + 1;
            } else {
                $seq = 1;
            }

            $code = $prefix . str_pad($seq, 2, '0', STR_PAD_LEFT);

            return $code;
        } finally {
            $lock->release();
        }
    }

    /**
     * 獲取指定的進貨單資訊 (實作 GoodsReceiptServiceInterface)
     *
     * @param int $goodsReceiptId
     * @return array|null 
     */
    public function getGoodsReceiptData(int $goodsReceiptId): ?array
    {
        $receipt = GoodsReceipt::with('items')->find($goodsReceiptId);
        
        if (!$receipt) {
            return null;
        }

        // 以陣列形式回傳資料，避免外部模組產生 Model 依賴
        return $receipt->toArray();
    }
}
