<?php

namespace App\Models\Machine;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

use App\Traits\TenantScoped;

class Machine extends Model
{
    use HasFactory, TenantScoped;

    protected static function booted()
    {
        // 權限隔離：一般帳號登入時只能看到自己被分配的機台
        static::addGlobalScope('machine_access', function (\Illuminate\Database\Eloquent\Builder $builder) {
            $user = auth()->user();
            // 如果是在 Console、或是系統管理員，則不限制 (可看所有機台)
            if (app()->runningInConsole() || !$user || $user->isSystemAdmin()) {
                return;
            }

            // 一般租戶帳號：限制只能看自己擁有的機台
            $builder->whereExists(function ($query) use ($user) {
                $query->select(\Illuminate\Support\Facades\DB::raw(1))
                    ->from('machine_user')
                    ->whereColumn('machine_user.machine_id', 'machines.id')
                    ->where('machine_user.user_id', $user->id);
            });
        });

        // 當 api_token 或 serial_no 發生變動時，自動同步至 Redis (供 MQTT 認證使用)
        static::saved(function ($machine) {
            if ($machine->wasChanged('api_token') || $machine->wasChanged('serial_no') || $machine->wasRecentlyCreated) {
                app(\App\Services\Machine\MachineService::class)->syncMqttAuth($machine);

                // 如果是修改序號，則刪除舊的 Redis Key 避免殘留
                if ($machine->wasChanged('serial_no') && !$machine->wasRecentlyCreated) {
                    $oldSerial = $machine->getOriginal('serial_no');
                    if ($oldSerial) {
                        \Illuminate\Support\Facades\Redis::del("machine_auth:{$oldSerial}");
                    }
                }
            }

            if ($machine->wasChanged([
                'is_spring_slot_1_10',
                'is_spring_slot_11_20',
                'is_spring_slot_21_30',
                'is_spring_slot_31_40',
                'is_spring_slot_41_50',
                'is_spring_slot_51_60',
            ])) {
                app(\App\Services\Machine\MachineService::class)->syncMachineSlotMaxStock($machine);
            }
        });
    }

    protected $fillable = [
        'company_id',
        'name',
        'serial_no',
        'model',
        'location',
        'address',
        'latitude',
        'longitude',
        'status',
        'current_page',
        'door_status',
        'temperature',
        'firmware_version',
        'api_token',
        'last_heartbeat_at',
        'card_reader_seconds',
        'card_reader_checkout_time_1',
        'card_reader_checkout_time_2',
        'heating_start_time',
        'heating_end_time',
        'payment_buffer_seconds',
        'card_reader_no',
        'key_no',
        'invoice_status',
        'welcome_gift_enabled',
        'is_spring_slot_1_10',
        'is_spring_slot_11_20',
        'is_spring_slot_21_30',
        'is_spring_slot_31_40',
        'is_spring_slot_41_50',
        'is_spring_slot_51_60',
        'member_system_enabled',
        'payment_config_id',
        'machine_model_id',
        'images',
        'creator_id',
        'updater_id',
        'tax_invoice_enabled',
        'card_terminal_enabled',
        'scan_pay_esun_enabled',
        'scan_pay_linepay_enabled',
        'shopping_cart_enabled',
        'cash_module_enabled',
        'settings',
    ];

    protected $appends = ['image_urls', 'calculated_status', 'hardware_status', 'latest_status_log_time', 'latest_hardware_log_time', 'is_unstable', 'recent_disconnection_count', 'has_login_warning'];

    /**
     * 動態計算機台當前狀態 (僅限連線與系統層級)
     * 優先讀取資料庫 status 欄位
     * 若在線，則進一步檢查過去 15 分鐘是否為異常
     */
    public function getCalculatedStatusAttribute(): string
    {
        // 判定離線
        if ($this->status === 'offline') {
            return 'offline';
        }

        // 判定異常 (僅檢查 type = 'status' 的未解決日誌)
        $hasRecentErrors = $this->logs()
            ->where('type', 'status')
            ->where('level', 'error')
            ->where('is_resolved', false)
            ->exists();

        if ($hasRecentErrors) {
            return 'error';
        }

        // 判定警告 (僅檢查 type = 'status' 的未解決日誌)
        // 優化：當機台在線時，自動忽略「斷線 (LWT)」警告，因為狀態已恢復
        $hasRecentWarnings = $this->logs()
            ->where('type', 'status')
            ->where('level', 'warning')
            ->where('is_resolved', false)
            ->where('message', '!=', 'Connection lost (LWT)')
            ->exists();

        if ($hasRecentWarnings) {
            return 'warning';
        }

        return $this->status ?? 'online';
    }

    /**
     * 判定機台目前是否有未解決的登入失敗警告 (需達 3 次未解決警告才亮起)
     */
    public function getHasLoginWarningAttribute(): bool
    {
        return $this->logs()
            ->where('type', 'login')
            ->where('level', 'warning')
            ->where('is_resolved', false)
            ->count() >= 3;
    }

    /**
     * 下位機/硬體健康狀態 (基於 type = 'submachine' 日誌)
     */
    public function getHardwareStatusAttribute(): string
    {
        // 判定異常
        $hasErrors = $this->logs()
            ->where('type', 'submachine')
            ->where('level', 'error')
            ->where('is_resolved', false)
            ->exists();

        if ($hasErrors) {
            return 'error';
        }

        // 判定警告
        $hasWarnings = $this->logs()
            ->where('type', 'submachine')
            ->where('level', 'warning')
            ->where('is_resolved', false)
            ->exists();

        if ($hasWarnings) {
            return 'warning';
        }

        return 'normal';
    }

    /**
     * 取得最新一筆未解決的系統狀態日誌時間
     */
    public function getLatestStatusLogTimeAttribute()
    {
        $query = $this->logs()
            ->where('type', 'status')
            ->whereIn('level', ['error', 'warning'])
            ->where('is_resolved', false);

        // 優化：若在線，不顯示已恢復的斷線時間
        if ($this->status === 'online') {
            $query->where('message', '!=', 'Connection lost (LWT)');
        }

        return $query->latest()->first()?->created_at;
    }

    /**
     * 取得最新一筆未解決的下位機硬體日誌時間
     */
    public function getLatestHardwareLogTimeAttribute()
    {
        return $this->logs()
            ->where('type', 'submachine')
            ->whereIn('level', ['error', 'warning'])
            ->where('is_resolved', false)
            ->latest()
            ->first()?->created_at;
    }

    /**
     * Scope: 判定在線 (讀取 status 欄位)
     */
    public function scopeOnline($query)
    {
        return $query->where('status', 'online');
    }

    /**
     * Scope: 判定離線 (讀取 status 欄位)
     */
    public function scopeOffline($query)
    {
        return $query->where('status', 'offline');
    }

    public function scopeHasError($query)
    {
        return $query->whereHas('logs', function ($q) {
            $q->where('is_resolved', false)
                ->where('level', 'error');
        });
    }

    /**
     * Scope: 判定是否有任何待處理的異常 (排除掉單純的連線中斷)
     * 只有當登入失敗次數達 3 次以上時，才列入待處理告警。
     */
    public function scopeHasAnyAlert($query)
    {
        return $query->where(function($outer) {
            // 1. 有任何非登入類型的待處理異常
            $outer->whereHas('logs', function ($q) {
                $q->where(function($sub) {
                    $sub->where('is_resolved', false)
                        ->orWhereNull('is_resolved');
                })
                ->whereIn('level', ['error', 'warning'])
                ->where('type', '!=', 'login')
                ->where('message', '!=', 'Connection lost (LWT)');
            })
            // 2. 或者有至少 3 筆未解決的登入異常
            ->orWhereHas('logs', function ($q) {
                $q->where('is_resolved', false)
                    ->where('type', 'login')
                    ->where('level', 'warning');
            }, '>=', 3);
        });
    }

    public function scopeHasStatusError($query)
    {
        return $query->whereHas('logs', function ($q) {
            $q->where('is_resolved', false)
                ->where('type', 'status')
                ->where('level', 'error');
        });
    }

    public function scopeHasHardwareError($query)
    {
        return $query->whereHas('logs', function ($q) {
            $q->where('is_resolved', false)
                ->where('type', 'submachine')
                ->where('level', 'error');
        });
    }

    /**
     * Scope: 判定運行中 (在線且無近期系統異常)
     */
    public function scopeRunning($query)
    {
        return $query->online()->whereDoesntHave('logs', function ($q) {
            $q->where('is_resolved', false)
                ->where('type', 'status')
                ->whereIn('level', ['error', 'warning']);
        });
    }

    /**
     * Scope: 判定異常在線 (在線且有近期異常)
     */
    public function scopeErrorOnline($query)
    {
        return $query->online()->hasError();
    }

    protected $casts = [
        'last_heartbeat_at' => 'datetime',
        'temperature' => 'integer',
        'welcome_gift_enabled' => 'boolean',
        'is_spring_slot_1_10' => 'boolean',
        'is_spring_slot_11_20' => 'boolean',
        'is_spring_slot_21_30' => 'boolean',
        'is_spring_slot_31_40' => 'boolean',
        'is_spring_slot_41_50' => 'boolean',
        'is_spring_slot_51_60' => 'boolean',
        'member_system_enabled' => 'boolean',
        'tax_invoice_enabled' => 'boolean',
        'card_terminal_enabled' => 'boolean',
        'scan_pay_esun_enabled' => 'boolean',
        'scan_pay_linepay_enabled' => 'boolean',
        'shopping_cart_enabled' => 'boolean',
        'cash_module_enabled' => 'boolean',
        'images' => 'array',
        'settings' => 'array',
    ];

    /**
     * Get machine images absolute URLs
     */
    public function getImageUrlsAttribute(): array
    {
        if (empty($this->images)) {
            return [];
        }

        return array_map(fn($path) => \Illuminate\Support\Facades\Storage::disk('public')->url($path), $this->images);
    }

    public function logs()
    {
        return $this->hasMany(MachineLog::class);
    }

    public function slots()
    {
        return $this->hasMany(MachineSlot::class);
    }

    public function commands()
    {
        return $this->hasMany(RemoteCommand::class);
    }

    public function machineModel()
    {
        return $this->belongsTo(MachineModel::class);
    }

    public function paymentConfig()
    {
        return $this->belongsTo(\App\Models\System\PaymentConfig::class);
    }

    public function creator()
    {
        return $this->belongsTo(\App\Models\System\User::class, 'creator_id');
    }

    public function updater()
    {
        return $this->belongsTo(\App\Models\System\User::class, 'updater_id');
    }

    public const PAGE_STATUSES = [
        '0' => 'Standby',
        '1' => 'Home Page',
        '2' => 'Vending Page',
        '3' => 'Admin Page',
        '4' => 'Replenishment Page',
        '5' => 'Tutorial Page',
        '6' => 'Purchasing',
        '7' => 'Locked Page',
        '60' => 'Dispense Success',
        '61' => 'Slot Test',
        '62' => 'Payment Selection',
        '63' => 'Waiting for Payment',
        '64' => 'Dispensing',
        '65' => 'Receipt Printing',
        '66' => 'Pass Code',
        '67' => 'Pickup Code',
        '68' => 'Message Display',
        '69' => 'Cancel Purchase',
        '610' => 'Purchase Finished',
        '611' => 'Welcome Gift',
        '612' => 'Dispense Failed',
    ];

    public function getCurrentPageLabelAttribute(): string
    {
        $code = (string) $this->current_page;
        $label = self::PAGE_STATUSES[$code] ?? $code;
        return __($label);
    }

    public function getIsUnstableAttribute(): bool
    {
        // 抖動偵測：1 小時內斷線次數 >= 3
        return $this->recent_disconnection_count >= 3;
    }

    public function getRecentDisconnectionCountAttribute(): int
    {
        return $this->logs()
            ->where('type', 'status')
            ->where('message', 'Connection lost (LWT)')
            ->where('created_at', '>=', now()->subHour())
            ->count();
    }

    public function users()
    {
        return $this->belongsToMany(\App\Models\System\User::class);
    }

    public function orders()
    {
        return $this->hasMany(\App\Models\Transaction\Order::class);
    }

    public function stockMovements()
    {
        return $this->hasMany(MachineStockMovement::class);
    }
}
