<?php

namespace App\Services;

use App\Models\Items\Item;
use App\Models\Production\WorkOrder;
use App\Models\Production\WorkOrderItem;
use App\Models\Tenants\Receiving;
use App\Models\Tenants\Stock;
use App\Models\Tenants\StockLot;
use App\Models\Tenants\StockTransaction;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class StockService extends Service
{
    /**
     * Item 타입 → 재고관리 라벨 매핑 (기본값)
     */
    public const ITEM_TYPE_LABELS = [
        'RM' => '원자재',
        'SM' => '부자재',
        'CS' => '소모품',
        'PT' => '부품',
        'SF' => '반제품',
    ];

    private TenantSettingService $tenantSettingService;

    public function __construct()
    {
        $this->tenantSettingService = app(TenantSettingService::class);
    }

    /**
     * 테넌트 설정에서 재고관리 품목유형 조회
     */
    private function getStockItemTypes(): array
    {
        return $this->tenantSettingService->getStockItemTypes();
    }

    /**
     * 재고 목록 조회 (Item 메인 + Stock LEFT JOIN)
     */
    public function index(array $params): LengthAwarePaginator
    {
        $tenantId = $this->tenantId();
        $stockItemTypes = $this->getStockItemTypes();

        // Item 테이블이 메인 (테넌트 설정 기반 품목유형)
        $query = Item::query()
            ->where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->with('stock');

        // 검색어 필터 (Item 기준)
        if (! empty($params['search'])) {
            $search = $params['search'];
            $query->where(function ($q) use ($search) {
                $q->where('items.code', 'like', "%{$search}%")
                    ->orWhere('items.name', 'like', "%{$search}%");
            });
        }

        // 품목유형 필터 (Item.item_type 기준: RM, SM, CS)
        if (! empty($params['item_type'])) {
            $query->where('items.item_type', strtoupper($params['item_type']));
        }

        // 품목 카테고리 필터 (Item.item_category: BENDING, SCREEN, STEEL 등)
        if (! empty($params['item_category'])) {
            $query->where('items.item_category', strtoupper($params['item_category']));
        }

        // 재고 상태 필터 (Stock.status)
        if (! empty($params['status'])) {
            $query->whereHas('stock', function ($q) use ($params) {
                $q->where('status', $params['status']);
            });
        }

        // 위치 필터 (Stock.location)
        if (! empty($params['location'])) {
            $query->whereHas('stock', function ($q) use ($params) {
                $q->where('location', 'like', "%{$params['location']}%");
            });
        }

        // 날짜 범위 필터 (해당 기간에 입출고 이력이 있는 품목만)
        if (! empty($params['start_date']) || ! empty($params['end_date'])) {
            $query->whereHas('stock', function ($stockQuery) use ($params) {
                $stockQuery->whereHas('transactions', function ($txQuery) use ($params) {
                    if (! empty($params['start_date'])) {
                        $txQuery->whereDate('created_at', '>=', $params['start_date']);
                    }
                    if (! empty($params['end_date'])) {
                        $txQuery->whereDate('created_at', '<=', $params['end_date']);
                    }
                });
            });
        }

        // 정렬
        $sortBy = $params['sort_by'] ?? 'code';
        $sortDir = $params['sort_dir'] ?? 'asc';

        // Item 테이블 기준 정렬 필드 매핑
        $sortMapping = [
            'item_code' => 'items.code',
            'item_name' => 'items.name',
            'item_type' => 'items.item_type',
            'code' => 'items.code',
            'name' => 'items.name',
        ];

        $sortColumn = $sortMapping[$sortBy] ?? 'items.code';
        $query->orderBy($sortColumn, $sortDir);

        // 페이지네이션
        $perPage = $params['per_page'] ?? 20;

        return $query->paginate($perPage);
    }

    /**
     * 재고 통계 조회 (Item 기준)
     */
    public function stats(): array
    {
        $tenantId = $this->tenantId();
        $stockItemTypes = $this->getStockItemTypes();

        // 전체 자재 품목 수 (Item 기준)
        $totalItems = Item::where('tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->count();

        // 재고 상태별 카운트 (Stock이 있는 Item 기준)
        $normalCount = Item::where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->whereHas('stock', function ($q) {
                $q->where('status', 'normal');
            })
            ->count();

        $lowCount = Item::where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->whereHas('stock', function ($q) {
                $q->where('status', 'low');
            })
            ->count();

        $outCount = Item::where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->whereHas('stock', function ($q) {
                $q->where('status', 'out');
            })
            ->count();

        // 재고 정보가 없는 Item 수
        $noStockCount = Item::where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->whereDoesntHave('stock')
            ->count();

        return [
            'total_items' => $totalItems,
            'normal_count' => $normalCount,
            'low_count' => $lowCount,
            'out_count' => $outCount,
            'no_stock_count' => $noStockCount,
        ];
    }

    /**
     * 재고 상세 조회 (Item 기준, LOT 포함)
     */
    public function show(int $id): Item
    {
        $tenantId = $this->tenantId();
        $stockItemTypes = $this->getStockItemTypes();

        return Item::query()
            ->where('tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->with(['stock.lots' => function ($query) {
                $query->orderBy('fifo_order');
            }])
            ->findOrFail($id);
    }

    /**
     * 품목코드로 재고 조회 (Item 기준)
     */
    public function findByItemCode(string $itemCode): ?Item
    {
        $tenantId = $this->tenantId();
        $stockItemTypes = $this->getStockItemTypes();

        return Item::query()
            ->where('tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->where('code', $itemCode)
            ->with('stock')
            ->first();
    }

    /**
     * 품목유형별 통계 (Item.item_type 기준)
     */
    public function statsByItemType(): array
    {
        $tenantId = $this->tenantId();
        $stockItemTypes = $this->getStockItemTypes();

        // Item 기준으로 통계 (테넌트 설정 기반 품목유형)
        $stats = Item::where('tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->selectRaw('item_type, COUNT(*) as count')
            ->groupBy('item_type')
            ->get()
            ->keyBy('item_type');

        // 재고 수량 합계 (Stock이 있는 경우)
        $stockQtys = Item::where('items.tenant_id', $tenantId)
            ->byItemTypes($stockItemTypes)
            ->join('stocks', 'items.id', '=', 'stocks.item_id')
            ->selectRaw('items.item_type, SUM(stocks.stock_qty) as total_qty')
            ->groupBy('items.item_type')
            ->get()
            ->keyBy('item_type');

        $result = [];
        foreach ($stockItemTypes as $key) {
            $label = self::ITEM_TYPE_LABELS[$key] ?? $key;
            $itemData = $stats->get($key);
            $stockData = $stockQtys->get($key);
            $result[$key] = [
                'label' => $label,
                'count' => $itemData?->count ?? 0,
                'total_qty' => $stockData?->total_qty ?? 0,
            ];
        }

        return $result;
    }

    // =====================================================
    // 재고 변동 이벤트 메서드 (입고/생산/출하 연동)
    // =====================================================

    /**
     * 입고 완료 시 재고 증가
     *
     * @param  Receiving  $receiving  입고 완료된 Receiving 레코드
     * @return StockLot 생성된 StockLot
     *
     * @throws \Exception item_id가 없는 경우
     */
    public function increaseFromReceiving(Receiving $receiving): StockLot
    {
        if (! $receiving->item_id) {
            throw new \Exception(__('error.stock.item_id_required'));
        }

        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($receiving, $tenantId, $userId) {
            // 1. Stock 조회 또는 생성
            $stock = $this->getOrCreateStock($receiving->item_id, $receiving);

            // 2. FIFO 순서 계산
            $fifoOrder = $this->getNextFifoOrder($stock->id);

            // 3. StockLot 생성
            $stockLot = new StockLot;
            $stockLot->tenant_id = $tenantId;
            $stockLot->stock_id = $stock->id;
            $stockLot->lot_no = $receiving->lot_no;
            $stockLot->fifo_order = $fifoOrder;
            $stockLot->receipt_date = $receiving->receiving_date;
            $stockLot->qty = $receiving->receiving_qty;
            $stockLot->reserved_qty = 0;
            $stockLot->available_qty = $receiving->receiving_qty;
            $stockLot->unit = $receiving->order_unit ?? 'EA';
            $stockLot->supplier = $receiving->supplier;
            $stockLot->supplier_lot = $receiving->supplier_lot;
            $stockLot->po_number = $receiving->order_no;
            $stockLot->location = $receiving->receiving_location;
            $stockLot->status = 'available';
            $stockLot->receiving_id = $receiving->id;
            $stockLot->created_by = $userId;
            $stockLot->updated_by = $userId;
            $stockLot->save();

            // 4. Stock 정보 갱신 (LOT 기반)
            $stock->refreshFromLots();

            // 5. 거래 이력 기록
            $this->recordTransaction(
                stock: $stock,
                type: StockTransaction::TYPE_IN,
                qty: $receiving->receiving_qty,
                reason: StockTransaction::REASON_RECEIVING,
                referenceType: 'receiving',
                referenceId: $receiving->id,
                lotNo: $receiving->lot_no,
                stockLotId: $stockLot->id
            );

            // 6. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'stock_increase',
                reason: 'receiving',
                referenceType: 'receiving',
                referenceId: $receiving->id,
                qtyChange: $receiving->receiving_qty,
                lotNo: $receiving->lot_no
            );

            Log::info('Stock increased from receiving', [
                'receiving_id' => $receiving->id,
                'item_id' => $receiving->item_id,
                'stock_id' => $stock->id,
                'stock_lot_id' => $stockLot->id,
                'qty' => $receiving->receiving_qty,
            ]);

            return $stockLot;
        });
    }

    /**
     * 생산 완료 시 완성품 재고 입고
     *
     * increaseFromReceiving()을 기반으로 구현.
     * 선생산(수주 없는 작업지시) 완료 시 양품을 재고로 적재.
     *
     * @param  WorkOrder  $workOrder  선생산 작업지시
     * @param  WorkOrderItem  $woItem  작업지시 품목
     * @param  float  $goodQty  양품 수량
     * @param  string  $lotNo  LOT 번호
     * @return StockLot 생성된 StockLot
     */
    public function increaseFromProduction(
        WorkOrder $workOrder,
        WorkOrderItem $woItem,
        float $goodQty,
        string $lotNo
    ): StockLot {
        if (! $woItem->item_id) {
            throw new \Exception(__('error.stock.item_id_required'));
        }

        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($workOrder, $woItem, $goodQty, $lotNo, $tenantId, $userId) {
            // 1. Stock 조회 또는 생성
            $stock = $this->getOrCreateStock($woItem->item_id);

            // 2. FIFO 순서 계산
            $fifoOrder = $this->getNextFifoOrder($stock->id);

            // 3. StockLot 생성
            $stockLot = new StockLot;
            $stockLot->tenant_id = $tenantId;
            $stockLot->stock_id = $stock->id;
            $stockLot->lot_no = $lotNo;
            $stockLot->fifo_order = $fifoOrder;
            $stockLot->receipt_date = now()->toDateString();
            $stockLot->qty = $goodQty;
            $stockLot->reserved_qty = 0;
            $stockLot->available_qty = $goodQty;
            $stockLot->unit = $woItem->unit ?? 'EA';
            $stockLot->supplier = null;
            $stockLot->supplier_lot = null;
            $stockLot->po_number = null;
            $stockLot->location = null;
            $stockLot->status = 'available';
            $stockLot->receiving_id = null;
            $stockLot->work_order_id = $workOrder->id;
            $stockLot->created_by = $userId;
            $stockLot->updated_by = $userId;
            $stockLot->save();

            // 4. Stock 합계 갱신
            $stock->refreshFromLots();

            // 5. 거래 이력 기록
            $this->recordTransaction(
                stock: $stock,
                type: StockTransaction::TYPE_IN,
                qty: $goodQty,
                reason: StockTransaction::REASON_PRODUCTION_OUTPUT,
                referenceType: 'work_order',
                referenceId: $workOrder->id,
                lotNo: $lotNo,
                stockLotId: $stockLot->id
            );

            // 6. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'production_in',
                reason: 'production_output',
                referenceType: 'work_order',
                referenceId: $workOrder->id,
                qtyChange: $goodQty,
                lotNo: $lotNo
            );

            Log::info('Stock increased from production', [
                'work_order_id' => $workOrder->id,
                'item_id' => $woItem->item_id,
                'stock_id' => $stock->id,
                'stock_lot_id' => $stockLot->id,
                'qty' => $goodQty,
                'lot_no' => $lotNo,
            ]);

            return $stockLot;
        });
    }

    /**
     * 입고 수정 시 재고 조정 (차이만큼 증감)
     *
     * - completed→completed 수량변경: 차이만큼 조정 (50→60 = +10)
     * - completed→대기: 전량 차감 (newQty = 0)
     *
     * @param  Receiving  $receiving  입고 레코드
     * @param  float  $newQty  새 수량 (상태가 completed가 아니면 0)
     */
    public function adjustFromReceiving(Receiving $receiving, float $newQty): void
    {
        if (! $receiving->item_id) {
            return;
        }

        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        DB::transaction(function () use ($receiving, $newQty, $tenantId, $userId) {
            // 1. 해당 입고로 생성된 StockLot 조회
            $stockLot = StockLot::where('tenant_id', $tenantId)
                ->where('receiving_id', $receiving->id)
                ->first();

            if (! $stockLot) {
                Log::warning('StockLot not found for receiving adjustment', [
                    'receiving_id' => $receiving->id,
                ]);

                return;
            }

            $stock = Stock::where('id', $stockLot->stock_id)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                return;
            }

            $oldQty = (float) $stockLot->qty;
            $diff = $newQty - $oldQty;

            // 차이가 없으면 스킵
            if (abs($diff) < 0.001) {
                return;
            }

            // 2. StockLot 수량 조정
            $stockLot->qty = $newQty;
            $stockLot->available_qty = max(0, $newQty - $stockLot->reserved_qty);
            $stockLot->updated_by = $userId;

            if ($newQty <= 0) {
                $stockLot->qty = 0;
                $stockLot->available_qty = 0;
                $stockLot->reserved_qty = 0;
                $stockLot->status = 'used';
            } else {
                $stockLot->status = 'available';
            }

            $stockLot->save();

            // 3. Stock 정보 갱신
            $stock->refreshFromLots();

            // 4. 거래 이력 기록
            $this->recordTransaction(
                stock: $stock,
                type: $diff > 0 ? StockTransaction::TYPE_IN : StockTransaction::TYPE_OUT,
                qty: $diff,
                reason: StockTransaction::REASON_RECEIVING,
                referenceType: 'receiving',
                referenceId: $receiving->id,
                lotNo: $receiving->lot_no,
                stockLotId: $stockLot->id
            );

            // 5. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: $diff > 0 ? 'stock_increase' : 'stock_decrease',
                reason: 'receiving_adjustment',
                referenceType: 'receiving',
                referenceId: $receiving->id,
                qtyChange: $diff,
                lotNo: $receiving->lot_no
            );

            Log::info('Stock adjusted from receiving modification', [
                'receiving_id' => $receiving->id,
                'item_id' => $receiving->item_id,
                'stock_id' => $stock->id,
                'old_qty' => $oldQty,
                'new_qty' => $newQty,
                'diff' => $diff,
            ]);
        });
    }

    /**
     * Stock 조회 또는 생성
     *
     * @param  int  $itemId  품목 ID
     * @param  Receiving|null  $receiving  입고 정보 (새 Stock 생성 시 사용)
     */
    public function getOrCreateStock(int $itemId, ?Receiving $receiving = null): Stock
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        $item = Item::where('tenant_id', $tenantId)
            ->findOrFail($itemId);

        // 1차: item_id로 조회 (SoftDeletes 포함)
        $stock = Stock::withTrashed()
            ->where('tenant_id', $tenantId)
            ->where('item_id', $itemId)
            ->first();

        // 2차: item_code로 조회 (unique key 기준, item_id가 다를 수 있음)
        if (! $stock) {
            $stock = Stock::withTrashed()
                ->where('tenant_id', $tenantId)
                ->where('item_code', $item->code)
                ->first();

            // item_id가 변경된 경우 업데이트
            if ($stock && $stock->item_id !== $itemId) {
                $stock->item_id = $itemId;
            }
        }

        if ($stock) {
            if ($stock->trashed()) {
                $stock->restore();
                $stock->status = 'out';
            }
            $stock->item_name = $item->name;
            $stock->updated_by = $userId;
            $stock->save();

            return $stock;
        }

        // Stock이 없으면 새로 생성
        $stock = new Stock;
        $stock->tenant_id = $tenantId;
        $stock->item_id = $itemId;
        $stock->item_code = $item->code;
        $stock->item_name = $item->name;
        $stock->item_type = $item->item_type;
        $stock->specification = $item->specification ?? $receiving?->specification;
        $stock->unit = $item->unit ?? $receiving?->order_unit ?? 'EA';
        $stock->stock_qty = 0;
        $stock->safety_stock = 0;
        $stock->reserved_qty = 0;
        $stock->available_qty = 0;
        $stock->lot_count = 0;
        $stock->location = $receiving?->receiving_location;
        $stock->status = 'out';
        $stock->created_by = $userId;
        $stock->updated_by = $userId;
        $stock->save();

        Log::info('New Stock created', [
            'stock_id' => $stock->id,
            'item_id' => $itemId,
            'item_code' => $item->code,
        ]);

        return $stock;
    }

    /**
     * 다음 FIFO 순서 계산
     *
     * @param  int  $stockId  Stock ID
     * @return int 다음 FIFO 순서
     */
    public function getNextFifoOrder(int $stockId): int
    {
        $maxOrder = StockLot::where('stock_id', $stockId)
            ->max('fifo_order');

        return ($maxOrder ?? 0) + 1;
    }

    /**
     * FIFO 기반 재고 차감
     *
     * @param  int  $itemId  품목 ID
     * @param  float  $qty  차감할 수량
     * @param  string  $reason  차감 사유 (work_order_input, shipment 등)
     * @param  int  $referenceId  참조 ID (작업지시 ID, 출하 ID 등)
     * @return array 차감된 LOT 정보 배열
     *
     * @throws \Exception 재고 부족 시
     */
    public function decreaseFIFO(int $itemId, float $qty, string $reason, int $referenceId): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($itemId, $qty, $reason, $referenceId, $tenantId, $userId) {
            // 1. Stock 조회
            $stock = Stock::where('tenant_id', $tenantId)
                ->where('item_id', $itemId)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                throw new \Exception(__('error.stock.not_found'));
            }

            // 2. 가용 재고 확인
            if ($stock->available_qty < $qty) {
                throw new \Exception(__('error.stock.insufficient_qty'));
            }

            // 3. FIFO 순서로 LOT 조회 (가용 수량이 있는 LOT만)
            $lots = StockLot::where('stock_id', $stock->id)
                ->where('status', '!=', 'used')
                ->where('available_qty', '>', 0)
                ->orderBy('fifo_order')
                ->lockForUpdate()
                ->get();

            if ($lots->isEmpty()) {
                throw new \Exception(__('error.stock.lot_not_available'));
            }

            // 4. FIFO 순서로 차감
            $remainingQty = $qty;
            $deductedLots = [];

            foreach ($lots as $lot) {
                if ($remainingQty <= 0) {
                    break;
                }

                $deductQty = min($lot->available_qty, $remainingQty);

                // LOT 수량 차감
                $lot->qty -= $deductQty;
                $lot->available_qty -= $deductQty;
                $lot->updated_by = $userId;

                // LOT 상태 업데이트
                if ($lot->qty <= 0) {
                    $lot->status = 'used';
                }

                $lot->save();

                $deductedLots[] = [
                    'lot_id' => $lot->id,
                    'lot_no' => $lot->lot_no,
                    'deducted_qty' => $deductQty,
                    'remaining_qty' => $lot->qty,
                ];

                $remainingQty -= $deductQty;
            }

            // 5. Stock 정보 갱신
            $oldStockQty = $stock->stock_qty;
            $stock->refreshFromLots();
            $stock->last_issue_date = now();
            $stock->save();

            // 6. 거래 이력 기록 (LOT별)
            foreach ($deductedLots as $dl) {
                $this->recordTransaction(
                    stock: $stock,
                    type: StockTransaction::TYPE_OUT,
                    qty: -$dl['deducted_qty'],
                    reason: $reason,
                    referenceType: $reason,
                    referenceId: $referenceId,
                    lotNo: $dl['lot_no'],
                    stockLotId: $dl['lot_id']
                );
            }

            // 7. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'stock_decrease',
                reason: $reason,
                referenceType: $reason,
                referenceId: $referenceId,
                qtyChange: -$qty,
                lotNo: implode(',', array_column($deductedLots, 'lot_no'))
            );

            Log::info('Stock decreased (FIFO)', [
                'item_id' => $itemId,
                'stock_id' => $stock->id,
                'qty' => $qty,
                'reason' => $reason,
                'reference_id' => $referenceId,
                'old_stock_qty' => $oldStockQty,
                'new_stock_qty' => $stock->stock_qty,
                'deducted_lots' => $deductedLots,
            ]);

            return $deductedLots;
        });
    }

    /**
     * 특정 StockLot에서 재고 차감
     *
     * 사용자가 선택한 특정 로트에서 지정 수량만큼 차감합니다.
     *
     * @param  int  $stockLotId  차감할 StockLot ID
     * @param  float  $qty  차감 수량
     * @param  string  $reason  차감 사유
     * @param  int  $referenceId  참조 ID
     * @return array 차감 결과
     *
     * @throws \Exception 재고 부족 또는 로트 없음
     */
    public function decreaseFromLot(int $stockLotId, float $qty, string $reason, int $referenceId): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($stockLotId, $qty, $reason, $referenceId, $tenantId, $userId) {
            // 1. StockLot 조회
            $lot = StockLot::where('tenant_id', $tenantId)
                ->where('id', $stockLotId)
                ->lockForUpdate()
                ->first();

            if (! $lot) {
                throw new \Exception(__('error.stock.lot_not_available'));
            }

            if ($lot->available_qty < $qty) {
                throw new \Exception(__('error.stock.insufficient_qty'));
            }

            // 2. Stock 조회
            $stock = Stock::where('id', $lot->stock_id)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                throw new \Exception(__('error.stock.not_found'));
            }

            $oldStockQty = $stock->stock_qty;

            // 3. LOT 수량 차감
            $lot->qty -= $qty;
            $lot->available_qty -= $qty;
            $lot->updated_by = $userId;

            if ($lot->qty <= 0) {
                $lot->status = 'used';
            }
            $lot->save();

            // 4. Stock 정보 갱신
            $stock->refreshFromLots();
            $stock->last_issue_date = now();
            $stock->save();

            // 5. 거래 이력 기록
            $this->recordTransaction(
                stock: $stock,
                type: StockTransaction::TYPE_OUT,
                qty: -$qty,
                reason: $reason,
                referenceType: $reason,
                referenceId: $referenceId,
                lotNo: $lot->lot_no,
                stockLotId: $lot->id
            );

            // 6. 감사 로그
            $this->logStockChange(
                stock: $stock,
                action: 'stock_decrease',
                reason: $reason,
                referenceType: $reason,
                referenceId: $referenceId,
                qtyChange: -$qty,
                lotNo: $lot->lot_no
            );

            Log::info('Stock decreased from specific lot', [
                'stock_lot_id' => $stockLotId,
                'lot_no' => $lot->lot_no,
                'qty' => $qty,
                'reason' => $reason,
                'reference_id' => $referenceId,
                'old_stock_qty' => $oldStockQty,
                'new_stock_qty' => $stock->stock_qty,
            ]);

            return [
                'lot_id' => $lot->id,
                'lot_no' => $lot->lot_no,
                'deducted_qty' => $qty,
                'remaining_qty' => $lot->qty,
            ];
        });
    }

    /**
     * 특정 LOT에 수량 복원 (투입 취소, 삭제 등)
     * decreaseFromLot의 역방향
     */
    public function increaseToLot(int $stockLotId, float $qty, string $reason, int $referenceId): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($stockLotId, $qty, $reason, $referenceId, $tenantId, $userId) {
            $lot = StockLot::where('tenant_id', $tenantId)
                ->where('id', $stockLotId)
                ->lockForUpdate()
                ->first();

            if (! $lot) {
                throw new \Exception(__('error.stock.lot_not_available'));
            }

            $stock = Stock::where('id', $lot->stock_id)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                throw new \Exception(__('error.stock.not_found'));
            }

            $oldStockQty = $stock->stock_qty;

            // LOT 수량 복원
            $lot->qty += $qty;
            $lot->available_qty += $qty;
            $lot->updated_by = $userId;

            if ($lot->status === 'used' && $lot->qty > 0) {
                $lot->status = 'available';
            }
            $lot->save();

            // Stock 정보 갱신
            $stock->refreshFromLots();

            // 거래 이력 기록
            $this->recordTransaction(
                stock: $stock,
                type: StockTransaction::TYPE_IN,
                qty: $qty,
                reason: $reason,
                referenceType: $reason,
                referenceId: $referenceId,
                lotNo: $lot->lot_no,
                stockLotId: $lot->id
            );

            // 감사 로그
            $this->logStockChange(
                stock: $stock,
                action: 'stock_increase',
                reason: $reason,
                referenceType: $reason,
                referenceId: $referenceId,
                qtyChange: $qty,
                lotNo: $lot->lot_no
            );

            Log::info('Stock increased to specific lot', [
                'stock_lot_id' => $stockLotId,
                'lot_no' => $lot->lot_no,
                'qty' => $qty,
                'reason' => $reason,
                'reference_id' => $referenceId,
                'old_stock_qty' => $oldStockQty,
                'new_stock_qty' => $stock->stock_qty,
            ]);

            return [
                'lot_id' => $lot->id,
                'lot_no' => $lot->lot_no,
                'restored_qty' => $qty,
                'remaining_qty' => $lot->qty,
            ];
        });
    }

    /**
     * 품목별 가용 재고 조회
     *
     * @param  int  $itemId  품목 ID
     * @return array|null 재고 정보 (stock_qty, available_qty, reserved_qty, lot_count)
     */
    public function getAvailableStock(int $itemId): ?array
    {
        $tenantId = $this->tenantId();

        $stock = Stock::where('tenant_id', $tenantId)
            ->where('item_id', $itemId)
            ->first();

        if (! $stock) {
            return null;
        }

        return [
            'stock_id' => $stock->id,
            'item_id' => $stock->item_id,
            'stock_qty' => (float) $stock->stock_qty,
            'available_qty' => (float) $stock->available_qty,
            'reserved_qty' => (float) $stock->reserved_qty,
            'lot_count' => $stock->lot_count,
            'status' => $stock->status,
        ];
    }

    // =====================================================
    // 재고 예약 메서드 (견적/수주 연동)
    // =====================================================

    /**
     * 재고 예약 (수주 확정 시)
     *
     * @param  int  $itemId  품목 ID
     * @param  float  $qty  예약할 수량
     * @param  int  $orderId  수주 ID (참조용)
     *
     * @throws \Exception 재고 부족 시
     */
    public function reserve(int $itemId, float $qty, int $orderId): void
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        DB::transaction(function () use ($itemId, $qty, $orderId, $tenantId, $userId) {
            // 1. Stock 조회 (락 적용)
            $stock = Stock::where('tenant_id', $tenantId)
                ->where('item_id', $itemId)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                // 재고가 없으면 예약만 기록 (재고 부족이지만 수주는 가능)
                Log::warning('Stock not found for reservation', [
                    'item_id' => $itemId,
                    'order_id' => $orderId,
                    'qty' => $qty,
                ]);

                return;
            }

            // 2. 가용 재고 확인 (경고만 기록, 예약은 진행)
            if ($stock->available_qty < $qty) {
                Log::warning('Insufficient stock for reservation', [
                    'item_id' => $itemId,
                    'order_id' => $orderId,
                    'requested_qty' => $qty,
                    'available_qty' => $stock->available_qty,
                ]);
            }

            // 3. FIFO 순서로 LOT 예약 처리
            $lots = StockLot::where('stock_id', $stock->id)
                ->where('status', '!=', 'used')
                ->where('available_qty', '>', 0)
                ->orderBy('fifo_order')
                ->lockForUpdate()
                ->get();

            $remainingQty = $qty;
            $reservedLots = [];

            foreach ($lots as $lot) {
                if ($remainingQty <= 0) {
                    break;
                }

                $reserveQty = min($lot->available_qty, $remainingQty);

                // LOT 예약 수량 증가
                $lot->reserved_qty += $reserveQty;
                $lot->available_qty -= $reserveQty;
                $lot->updated_by = $userId;

                // 상태 업데이트 (전량 예약 시)
                if ($lot->available_qty <= 0) {
                    $lot->status = 'reserved';
                }

                $lot->save();

                $reservedLots[] = [
                    'lot_id' => $lot->id,
                    'lot_no' => $lot->lot_no,
                    'reserved_qty' => $reserveQty,
                ];

                $remainingQty -= $reserveQty;
            }

            // 4. Stock 정보 갱신
            $stock->refreshFromLots();

            // 5. 거래 이력 기록 (LOT별)
            foreach ($reservedLots as $rl) {
                $this->recordTransaction(
                    stock: $stock,
                    type: StockTransaction::TYPE_RESERVE,
                    qty: $rl['reserved_qty'],
                    reason: StockTransaction::REASON_ORDER_CONFIRM,
                    referenceType: 'order',
                    referenceId: $orderId,
                    lotNo: $rl['lot_no'],
                    stockLotId: $rl['lot_id']
                );
            }

            // 6. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'stock_reserve',
                reason: 'order_confirm',
                referenceType: 'order',
                referenceId: $orderId,
                qtyChange: $qty,
                lotNo: implode(',', array_column($reservedLots, 'lot_no'))
            );

            Log::info('Stock reserved for order', [
                'order_id' => $orderId,
                'item_id' => $itemId,
                'qty' => $qty,
                'reserved_lots' => $reservedLots,
            ]);
        });
    }

    /**
     * 재고 예약 해제 (수주 취소 시)
     *
     * @param  int  $itemId  품목 ID
     * @param  float  $qty  해제할 수량
     * @param  int  $orderId  수주 ID (참조용)
     */
    public function releaseReservation(int $itemId, float $qty, int $orderId): void
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        DB::transaction(function () use ($itemId, $qty, $orderId, $tenantId, $userId) {
            // 1. Stock 조회
            $stock = Stock::where('tenant_id', $tenantId)
                ->where('item_id', $itemId)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                Log::warning('Stock not found for release reservation', [
                    'item_id' => $itemId,
                    'order_id' => $orderId,
                ]);

                return;
            }

            // 2. 예약된 LOT 조회 (FIFO 역순으로 해제)
            $lots = StockLot::where('stock_id', $stock->id)
                ->where('reserved_qty', '>', 0)
                ->orderByDesc('fifo_order')
                ->lockForUpdate()
                ->get();

            $remainingQty = $qty;
            $releasedLots = [];

            foreach ($lots as $lot) {
                if ($remainingQty <= 0) {
                    break;
                }

                $releaseQty = min($lot->reserved_qty, $remainingQty);

                // LOT 예약 해제
                $lot->reserved_qty -= $releaseQty;
                $lot->available_qty += $releaseQty;
                $lot->updated_by = $userId;

                // 상태 업데이트
                if ($lot->qty > 0 && $lot->status === 'reserved') {
                    $lot->status = 'available';
                }

                $lot->save();

                $releasedLots[] = [
                    'lot_id' => $lot->id,
                    'lot_no' => $lot->lot_no,
                    'released_qty' => $releaseQty,
                ];

                $remainingQty -= $releaseQty;
            }

            // 3. Stock 정보 갱신
            $stock->refreshFromLots();

            // 4. 거래 이력 기록 (LOT별)
            foreach ($releasedLots as $rl) {
                $this->recordTransaction(
                    stock: $stock,
                    type: StockTransaction::TYPE_RELEASE,
                    qty: -$rl['released_qty'],
                    reason: StockTransaction::REASON_ORDER_CANCEL,
                    referenceType: 'order',
                    referenceId: $orderId,
                    lotNo: $rl['lot_no'],
                    stockLotId: $rl['lot_id']
                );
            }

            // 5. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'stock_release',
                reason: 'order_cancel',
                referenceType: 'order',
                referenceId: $orderId,
                qtyChange: -$qty,
                lotNo: implode(',', array_column($releasedLots, 'lot_no'))
            );

            Log::info('Stock reservation released', [
                'order_id' => $orderId,
                'item_id' => $itemId,
                'qty' => $qty,
                'released_lots' => $releasedLots,
            ]);
        });
    }

    /**
     * 수주 품목들의 재고 예약 (일괄 처리)
     *
     * @param  \Illuminate\Support\Collection  $orderItems  수주 품목 목록
     * @param  int  $orderId  수주 ID
     */
    public function reserveForOrder($orderItems, int $orderId): void
    {
        foreach ($orderItems as $item) {
            if (! $item->item_id) {
                continue;
            }

            $this->reserve(
                itemId: $item->item_id,
                qty: (float) $item->quantity,
                orderId: $orderId
            );
        }
    }

    /**
     * 수주 품목들의 재고 예약 해제 (일괄 처리)
     *
     * @param  \Illuminate\Support\Collection  $orderItems  수주 품목 목록
     * @param  int  $orderId  수주 ID
     */
    public function releaseReservationForOrder($orderItems, int $orderId): void
    {
        foreach ($orderItems as $item) {
            if (! $item->item_id) {
                continue;
            }

            $this->releaseReservation(
                itemId: $item->item_id,
                qty: (float) $item->quantity,
                orderId: $orderId
            );
        }
    }

    // =====================================================
    // 출하 연동 메서드
    // =====================================================

    /**
     * 출하 완료 시 재고 차감
     *
     * @param  int  $itemId  품목 ID
     * @param  float  $qty  차감할 수량
     * @param  int  $shipmentId  출하 ID
     * @param  int|null  $stockLotId  특정 LOT ID (지정 시 해당 LOT만 차감)
     * @return array 차감된 LOT 정보
     */
    public function decreaseForShipment(int $itemId, float $qty, int $shipmentId, ?int $stockLotId = null): array
    {
        $tenantId = $this->tenantId();
        $userId = $this->apiUserId();

        return DB::transaction(function () use ($itemId, $qty, $shipmentId, $stockLotId, $tenantId, $userId) {
            // 1. Stock 조회
            $stock = Stock::where('tenant_id', $tenantId)
                ->where('item_id', $itemId)
                ->lockForUpdate()
                ->first();

            if (! $stock) {
                throw new \Exception(__('error.stock.not_found'));
            }

            // 2. LOT 조회 (특정 LOT 또는 FIFO)
            if ($stockLotId) {
                $lots = StockLot::where('id', $stockLotId)
                    ->where('stock_id', $stock->id)
                    ->lockForUpdate()
                    ->get();
            } else {
                $lots = StockLot::where('stock_id', $stock->id)
                    ->where('status', '!=', 'used')
                    ->where('qty', '>', 0)
                    ->orderBy('fifo_order')
                    ->lockForUpdate()
                    ->get();
            }

            if ($lots->isEmpty()) {
                throw new \Exception(__('error.stock.lot_not_available'));
            }

            // 3. 차감 처리
            $remainingQty = $qty;
            $deductedLots = [];

            foreach ($lots as $lot) {
                if ($remainingQty <= 0) {
                    break;
                }

                $deductQty = min($lot->qty, $remainingQty);

                // LOT 수량 차감
                $lot->qty -= $deductQty;

                // 예약 수량도 동시에 차감 (출하는 예약된 재고를 사용)
                if ($lot->reserved_qty > 0) {
                    $reserveDeduct = min($lot->reserved_qty, $deductQty);
                    $lot->reserved_qty -= $reserveDeduct;
                } else {
                    // 예약 없이 출하되는 경우 available에서 차감
                    $lot->available_qty = max(0, $lot->available_qty - $deductQty);
                }

                $lot->updated_by = $userId;

                // LOT 상태 업데이트
                if ($lot->qty <= 0) {
                    $lot->status = 'used';
                    $lot->available_qty = 0;
                    $lot->reserved_qty = 0;
                }

                $lot->save();

                $deductedLots[] = [
                    'lot_id' => $lot->id,
                    'lot_no' => $lot->lot_no,
                    'deducted_qty' => $deductQty,
                    'remaining_qty' => $lot->qty,
                ];

                $remainingQty -= $deductQty;
            }

            // 4. Stock 정보 갱신
            $stock->refreshFromLots();
            $stock->last_issue_date = now();
            $stock->save();

            // 5. 거래 이력 기록 (LOT별)
            foreach ($deductedLots as $dl) {
                $this->recordTransaction(
                    stock: $stock,
                    type: StockTransaction::TYPE_OUT,
                    qty: -$dl['deducted_qty'],
                    reason: StockTransaction::REASON_SHIPMENT,
                    referenceType: 'shipment',
                    referenceId: $shipmentId,
                    lotNo: $dl['lot_no'],
                    stockLotId: $dl['lot_id']
                );
            }

            // 6. 감사 로그 기록
            $this->logStockChange(
                stock: $stock,
                action: 'stock_decrease',
                reason: 'shipment',
                referenceType: 'shipment',
                referenceId: $shipmentId,
                qtyChange: -$qty,
                lotNo: implode(',', array_column($deductedLots, 'lot_no'))
            );

            Log::info('Stock decreased for shipment', [
                'shipment_id' => $shipmentId,
                'item_id' => $itemId,
                'qty' => $qty,
                'deducted_lots' => $deductedLots,
            ]);

            return $deductedLots;
        });
    }

    /**
     * 재고 거래 이력 기록
     *
     * @param  Stock  $stock  재고
     * @param  string  $type  거래유형 (IN, OUT, RESERVE, RELEASE)
     * @param  float  $qty  변동 수량 (입고: 양수, 출고: 음수)
     * @param  string  $reason  사유
     * @param  string  $referenceType  참조 유형
     * @param  int  $referenceId  참조 ID
     * @param  string|null  $lotNo  LOT번호
     * @param  int|null  $stockLotId  StockLot ID
     * @param  string|null  $remark  비고
     */
    private function recordTransaction(
        Stock $stock,
        string $type,
        float $qty,
        string $reason,
        string $referenceType,
        int $referenceId,
        ?string $lotNo = null,
        ?int $stockLotId = null,
        ?string $remark = null
    ): void {
        try {
            StockTransaction::create([
                'tenant_id' => $stock->tenant_id,
                'stock_id' => $stock->id,
                'stock_lot_id' => $stockLotId,
                'type' => $type,
                'qty' => $qty,
                'balance_qty' => (float) $stock->stock_qty,
                'reference_type' => $referenceType,
                'reference_id' => $referenceId,
                'lot_no' => $lotNo,
                'reason' => $reason,
                'remark' => $remark,
                'item_code' => $stock->item_code,
                'item_name' => $stock->item_name,
                'created_by' => $this->apiUserId(),
            ]);
        } catch (\Exception $e) {
            // 거래 이력 기록 실패는 비즈니스 로직에 영향을 주지 않음
            Log::warning('Failed to record stock transaction', [
                'stock_id' => $stock->id,
                'type' => $type,
                'qty' => $qty,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * 재고 변경 감사 로그 기록
     */
    private function logStockChange(
        Stock $stock,
        string $action,
        string $reason,
        string $referenceType,
        int $referenceId,
        float $qtyChange,
        ?string $lotNo = null
    ): void {
        try {
            \App\Models\Audit\AuditLog::create([
                'tenant_id' => $stock->tenant_id,
                'target_type' => 'Stock',
                'target_id' => $stock->id,
                'action' => $action,
                'before' => [
                    'stock_qty' => (float) ($stock->getOriginal('stock_qty') ?? 0),
                ],
                'after' => [
                    'stock_qty' => (float) $stock->stock_qty,
                    'qty_change' => $qtyChange,
                    'reason' => $reason,
                    'reference_type' => $referenceType,
                    'reference_id' => $referenceId,
                    'lot_no' => $lotNo,
                ],
                'actor_id' => $this->apiUserId(),
                'ip' => request()->ip(),
                'ua' => request()->userAgent(),
                'created_at' => now(),
            ]);
        } catch (\Exception $e) {
            // 감사 로그 실패는 비즈니스 로직에 영향을 주지 않음
            Log::warning('Failed to create audit log for stock change', [
                'stock_id' => $stock->id,
                'action' => $action,
                'error' => $e->getMessage(),
            ]);
        }
    }
}
