<?php

namespace App\Services\Machine;

use App\Models\Machine\Machine;
use App\Models\Machine\MachineLog;
use App\Models\Machine\RemoteCommand;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

class MachineService
{
    /**
     * B013: 硬體狀態代碼對照表 (Hardware Status Code Mapping)
     */
    public const ERROR_CODE_MAP = [
        // 出貨狀態類 (Prefix: 04 - BUY_STATUS)
        '0401' => ['label' => 'Dispensing in progress', 'level' => 'info'],
        '0402' => ['label' => 'Dispense successful', 'level' => 'info'],
        '0403' => ['label' => 'Slot jammed', 'level' => 'error'],
        '0404' => ['label' => 'Motor not stopped', 'level' => 'warning'],
        '0406' => ['label' => 'Slot not found', 'level' => 'error'],
        '0407' => ['label' => 'Dispense error (0407)', 'level' => 'error'],
        '0408' => ['label' => 'Dispense error (0408)', 'level' => 'error'],
        '0409' => ['label' => 'Dispense error (0409)', 'level' => 'error'],
        '040A' => ['label' => 'Dispense error (040A)', 'level' => 'error'],
        '0410' => ['label' => 'Elevator rising', 'level' => 'info'],
        '0411' => ['label' => 'Elevator descending', 'level' => 'info'],
        '0412' => ['label' => 'Elevator rise error', 'level' => 'error'],
        '0413' => ['label' => 'Elevator descent error', 'level' => 'error'],
        '0414' => ['label' => 'Pickup door closed', 'level' => 'info'],
        '0415' => ['label' => 'Pickup door error', 'level' => 'error'],
        '0416' => ['label' => 'Delivery door opened', 'level' => 'info'],
        '0417' => ['label' => 'Delivery door open error', 'level' => 'error'],
        '0418' => ['label' => 'Delivering product', 'level' => 'info'],
        '0419' => ['label' => 'Delivery door closed', 'level' => 'info'],
        '0420' => ['label' => 'Delivery door close error', 'level' => 'error'],
        '0421' => ['label' => 'Hopper empty', 'level' => 'warning'],
        '0422' => ['label' => 'Hopper overheated', 'level' => 'warning'],
        '0423' => ['label' => 'Hopper heating timeout', 'level' => 'error'],
        '0424' => ['label' => 'Hopper error (0424)', 'level' => 'error'],
        '0426' => ['label' => 'Microwave door opened', 'level' => 'info'],
        '0427' => ['label' => 'Microwave door error', 'level' => 'error'],
        '04FF' => ['label' => 'Dispense stopped', 'level' => 'info'],

        // 貨道狀態類 (Prefix: 02 - SLOT_STATUS)
        '0201' => ['label' => 'Slot normal', 'level' => 'info'],
        '0202' => ['label' => 'Product empty', 'level' => 'warning'],
        '0203' => ['label' => 'Slot empty', 'level' => 'warning'],
        '0206' => ['label' => 'Slot not closed', 'level' => 'warning'],
        '0207' => ['label' => 'Slot motor error (0207)', 'level' => 'error'],
        '0208' => ['label' => 'Slot motor error (0208)', 'level' => 'error'],
        '0209' => ['label' => 'Slot motor error (0209)', 'level' => 'error'],
        '0212' => ['label' => 'Hopper empty (0212)', 'level' => 'warning'],

        // 機台整體狀態類 (Prefix: 54 - MACHINE_STATUS)
        '5400' => ['label' => 'Machine normal', 'level' => 'info'],
        '5401' => ['label' => 'Elevator sensor error', 'level' => 'error'],
        '5402' => ['label' => 'Pickup door not closed', 'level' => 'warning'],
        '5403' => ['label' => 'Elevator failure', 'level' => 'error'],
    ];

    /**
     * Update machine heartbeat and status.
     * 
     * @param string $serialNo
     * @param array $data
     * @return Machine
     */
    public function updateHeartbeat(string $serialNo, array $data): Machine
    {
        return DB::transaction(function () use ($serialNo, $data) {
            $machine = Machine::where('serial_no', $serialNo)->firstOrFail();

            // 採用現代化語意命名 (Modern semantic naming)
            $temperature = $data['temperature'] ?? $machine->temperature;
            $currentPage = $data['current_page'] ?? $machine->current_page;
            $doorStatus = $data['door_status'] ?? $machine->door_status;
            $firmwareVersion = $data['firmware_version'] ?? $machine->firmware_version;
            $model = $data['model'] ?? $machine->model;

            $updateData = [
                'temperature' => $temperature,
                'current_page' => $currentPage,
                'door_status' => $doorStatus,
                'firmware_version' => $firmwareVersion,
                'model' => $model,
                'last_heartbeat_at' => now(),
            ];

            $machine->update($updateData);

            // Record log if provided
            if (!empty($data['log'])) {
                $machine->logs()->create([
                    'company_id' => $machine->company_id,
                    'type' => 'status',
                    'level' => $data['log_level'] ?? 'info',
                    'message' => $data['log'],
                    'context' => $data['log_payload'] ?? null,
                ]);
            }

            return $machine;
        });
    }

    /**
     * Sync machine slots based on replenishment report.
     * 
     * @param Machine $machine
     * @param array $slotsData
     */
    public function syncSlots(Machine $machine, array $slotsData): void
    {
        DB::transaction(function () use ($machine, $slotsData) {
            // 蒐集所有傳入的商品 ID (可能是 SKU 或 實際 ID)
            $productCodes = collect($slotsData)->pluck('product_id')->filter()->unique()->toArray();
            
            // 優先以 ID 查詢商品，若 ID 不存在則嘗試 Barcode (Prioritize ID lookup, fallback to Barcode)
            $products = \App\Models\Product\Product::whereIn('id', $productCodes)
                ->orWhereIn('barcode', $productCodes)
                ->get();

            foreach ($slotsData as $slotData) {
                $slotNo = $slotData['slot_no'] ?? null;
                if (!$slotNo) continue;

                $existingSlot = $machine->slots()->where('slot_no', $slotNo)->first();
                
                // 查找對應的實體 ID (支援 ID 與 Barcode 比對)
                $productCode = $slotData['product_id'] ?? null;
                $actualProductId = null;
                if ($productCode) {
                    $actualProductId = $products->first(function ($p) use ($productCode) {
                        return (string)$p->id === (string)$productCode || $p->barcode === (string)$productCode;
                    })?->id;
                }

                // 根據貨道類型自動決定上限 (Auto-calculate max_stock based on slot type)
                // 若未提供 type，預設為 '1' (履帶/Track)
                $slotType = $slotData['type'] ?? $existingSlot->type ?? '1';
                if ($actualProductId) {
                    $product = $products->find($actualProductId);
                    if ($product) {
                        // 1: 履帶, 2: 彈簧
                        $calculatedMaxStock = ($slotType == '1') ? $product->track_limit : $product->spring_limit;
                        $slotData['capacity'] = $calculatedMaxStock ?? $slotData['capacity'] ?? null;
                    }
                }

                $updateData = [
                    'product_id' => $actualProductId,
                    'type' => $slotType,
                    'stock' => $slotData['stock'] ?? 0,
                    'max_stock' => $slotData['capacity'] ?? ($existingSlot->max_stock ?? 10),
                    'is_active' => true,
                ];

                // 如果這是一次明確的補貨回報，建議更新時間並記錄
                if ($existingSlot) {
                    $existingSlot->update($updateData);
                } else {
                    $machine->slots()->create(array_merge($updateData, ['slot_no' => $slotNo]));
                }
            }
        });
    }

    /**
     * Update machine slot stock, expiry, and batch.
     * 
     * @param Machine $machine
     * @param array $data
     * @param int|null $userId
     * @return void
     */
    public function updateSlot(Machine $machine, array $data, ?int $userId = null): void
    {
        DB::transaction(function () use ($machine, $data, $userId) {
            $slotNo = $data['slot_no'];
            $stock = $data['stock'] ?? null;
            $expiryDate = $data['expiry_date'] ?? null;
            $batchNo = $data['batch_no'] ?? null;
            $slot = $machine->slots()->where('slot_no', $slotNo)->firstOrFail();

            // 紀錄舊數據以供 Payload 使用
            $oldData = [
                'stock' => $slot->stock,
                'expiry_date' => $slot->expiry_date ? Carbon::parse($slot->expiry_date)->toDateString() : null,
                'batch_no' => $slot->batch_no,
            ];

            $updateData = [
                'expiry_date' => $expiryDate,
                'batch_no' => $batchNo,
            ];
            if ($stock !== null) $updateData['stock'] = (int)$stock;
            
            $slot->update($updateData);

            // 指令去重：將該機台所有尚未領取的舊庫存同步指令標記為「已取代」
            RemoteCommand::where('machine_id', $machine->id)
                ->where('command_type', 'reload_stock')
                ->where('status', 'pending')
                ->update([
                    'status' => 'superseded',
                    'note' => __('Superseded by new adjustment'),
                    'executed_at' => now(),
                ]);

            // 建立遠端指令紀錄 (Unified Command Concept)
            RemoteCommand::create([
                'machine_id' => $machine->id,
                'user_id' => $userId,
                'command_type' => 'reload_stock',
                'status' => 'pending',
                'payload' => [
                    'slot_no' => $slotNo,
                    'old' => $oldData,
                    'new' => [
                        'stock' => $stock !== null ? (int)$stock : $oldData['stock'],
                        'expiry_date' => $expiryDate ?: null,
                        'batch_no' => $batchNo ?: null,
                    ]
                ]
            ]);
        });
    }

    /**
     * B013: Record machine hardware error/status log with auto-translation.
     * 
     * @param Machine $machine
     * @param array $data
     * @return MachineLog
     */
    public function recordErrorLog(Machine $machine, array $data): MachineLog
    {
        $errorCode = $data['error_code'] ?? '0000';
        $mapping = self::ERROR_CODE_MAP[$errorCode] ?? ['label' => 'Unknown Status', 'level' => 'error'];
        
        $slotNo = $data['tid'] ?? null;
        $label = $mapping['label'];
        
        // 儲存原始英文格式作為 DB 備用，前端顯示會優先使用 model accessor 的動態翻譯內容
        $message = $slotNo ? "Slot {$slotNo}: {$label} (Code: {$errorCode})" : "{$label} (Code: {$errorCode})";

        return $machine->logs()->create([
            'company_id' => $machine->company_id,
            'type' => 'submachine',
            'level' => $mapping['level'],
            'message' => $message,
            'context' => array_merge($data, [
                'translated_label' => $label,
                'raw_code' => $errorCode
            ]),
        ]);
    }

    /**
     * Update machine slot stock (single slot).
     * Legacy support for recordLog (Existing code).
     */
    public function recordLog(int $machineId, array $data): MachineLog
    {
        $machine = Machine::findOrFail($machineId);

        return $machine->logs()->create([
            'level' => $data['level'] ?? 'info',
            'message' => $data['message'],
            'context' => $data['context'] ?? null,
        ]);
    }

    /**
     * Get machine utilization and OEE statistics for entire fleet.
     */
    public function getFleetStats(string $date): array
    {
        $start = Carbon::parse($date)->startOfDay();
        $end = Carbon::parse($date)->endOfDay();

        // 1. Online Count (Base on new heartbeat logic)
        $machines = Machine::all(); // This is filtered by TenantScoped
        $totalMachines = $machines->count();
        $onlineCount = Machine::online()->count();

        $machineIds = $machines->pluck('id')->toArray();

        // 2. Total Daily Sales (Sum of B600 logs across all authorized machines)
        $totalSales = MachineLog::whereIn('machine_id', $machineIds)
            ->where('message', 'like', '%B600%')
            ->whereBetween('created_at', [$start, $end])
            ->count();

        // 3. Average OEE (Simulated based on individual machine stats for performance)
        $totalOee = 0;
        $count = 0;
        
        foreach ($machines as $machine) {
            $stats = $this->getUtilizationStats($machine, $date);
            $totalOee += $stats['overview']['oee'];
            $count++;
        }

        $avgOee = ($count > 0) ? ($totalOee / $count) : 0;

        return [
            'avgOee' => round($avgOee, 2),
            'onlineCount' => $onlineCount,
            'totalMachines' => $totalMachines,
            'totalSales' => $totalSales,
            'alertCount' => MachineLog::whereIn('machine_id', $machineIds)
                ->where('level', 'error')
                ->whereBetween('created_at', [$start, $end])
                ->count()
        ];
    }

    /**
     * Get machine utilization and OEE statistics.
     */
    public function getUtilizationStats(Machine $machine, string $date): array
    {
        $start = Carbon::parse($date)->startOfDay();
        $end = Carbon::parse($date)->endOfDay();

        // 1. Availability: Based on heartbeat logs (status type)
        // Assume online if heartbeat within 6 minutes
        $logs = $machine->logs()
            ->where('type', 'status')
            ->whereBetween('created_at', [$start, $end])
            ->orderBy('created_at')
            ->get();

        $onlineMinutes = 0;
        $lastLogTime = null;

        foreach ($logs as $log) {
            $currentTime = Carbon::parse($log->created_at);
            if ($lastLogTime) {
                $diff = $currentTime->diffInMinutes($lastLogTime);
                if ($diff <= 6) {
                    $onlineMinutes += $diff;
                }
            }
            $lastLogTime = $currentTime;
        }

        $totalMinutes = 24 * 60;
        $availability = ($totalMinutes > 0) ? min(100, ($onlineMinutes / $totalMinutes) * 100) : 0;

        // 2. Performance: Sales Count (B600)
        // Target: 2 sales per hour (48/day)
        $salesCount = $machine->logs()
            ->where('message', 'like', '%B600%')
            ->whereBetween('created_at', [$start, $end])
            ->count();
        
        $targetSales = 48;
        $performance = ($targetSales > 0) ? min(100, ($salesCount / $targetSales) * 100) : 0;

        // 3. Quality: Success Rate
        // Exclude failed dispense (B130)
        $errorCount = $machine->logs()
            ->where('message', 'like', '%B130%')
            ->whereBetween('created_at', [$start, $end])
            ->count();
        
        $totalAttempts = $salesCount + $errorCount;
        $quality = ($totalAttempts > 0) ? (($salesCount / $totalAttempts) * 100) : 100;

        // Combined OEE
        $oee = ($availability / 100) * ($performance / 100) * ($quality / 100) * 100;

        return [
            'overview' => [
                'availability' => round($availability, 2),
                'performance' => round($performance, 2),
                'quality' => round($quality, 2),
                'oee' => round($oee, 2),
                'onlineHours' => round($onlineMinutes / 60, 2),
                'salesCount' => $salesCount,
                'errorCount' => $errorCount,
            ],
            'chart' => [
                'uptime' => $this->formatUptimeTimeline($logs, $start, $end),
                'sales' => $this->formatSalesTimeline($machine, $start, $end)
            ]
        ];
    }

    private function formatUptimeTimeline($logs, $start, $end)
    {
        $data = [];
        if ($logs->isEmpty()) return $data;

        $lastLog = null;
        $currentRangeStart = null;

        foreach ($logs as $log) {
            $logTime = Carbon::parse($log->created_at);
            if (!$currentRangeStart) {
                $currentRangeStart = $logTime;
            } else {
                $diff = $logTime->diffInMinutes(Carbon::parse($lastLog->created_at));
                if ($diff > 10) { // Interruption > 10 mins
                    $data[] = [
                        'x' => 'Uptime',
                        'y' => [$currentRangeStart->getTimestamp() * 1000, Carbon::parse($lastLog->created_at)->getTimestamp() * 1000],
                        'fillColor' => '#06b6d4'
                    ];
                    $currentRangeStart = $logTime;
                }
            }
            $lastLog = $log;
        }

        if ($currentRangeStart && $lastLog) {
            $data[] = [
                'x' => 'Uptime',
                'y' => [$currentRangeStart->getTimestamp() * 1000, Carbon::parse($lastLog->created_at)->getTimestamp() * 1000],
                'fillColor' => '#06b6d4'
            ];
        }

        return $data;
    }

    private function formatSalesTimeline($machine, $start, $end)
    {
        return $machine->logs()
            ->where('message', 'like', '%B600%')
            ->whereBetween('created_at', [$start, $end])
            ->get()
            ->map(function($log) {
                return [Carbon::parse($log->created_at)->getTimestamp() * 1000, 1];
            })
            ->toArray();
    }
}
