<?php

/**
 * ModpackChecker Cron Command — hybrid "Magic & Manual" detection.
 *
 * 1. Egg variables (fastest)
 * 2. File detection via DaemonFileRepository (manifest.json, modrinth.index.json)
 * 3. API version check for detected packs
 *
 * USAGE: php artisan modpackchecker:check
 * CRON:  0 0,6,12,18 * * * cd /var/www/pterodactyl && php artisan modpackchecker:check
 */

namespace Pterodactyl\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
use Pterodactyl\Services\ModpackApiService;

class CheckModpackUpdates extends Command
{
    protected $signature = 'modpackchecker:check';
    protected $description = 'Check all servers for modpack updates (hybrid detection)';

    public function __construct(
        private ModpackApiService $apiService,
        private DaemonFileRepository $fileRepository
    ) {
        parent::__construct();
    }

    public function handle(): int
    {
        $this->info('Starting modpack update check (hybrid detection)...');

        $servers = Server::all();
        $this->info("Scanning {$servers->count()} servers");

        foreach ($servers as $server) {
            $this->processServer($server);
            sleep(2);
        }

        $this->info('Modpack update check complete!');
        return 0;
    }

    private function processServer(Server $server): void
    {
        $this->line("Checking: {$server->name} ({$server->uuid})");

        // Skip user-overridden servers (manual config)
        $existing = DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->first();

        if ($existing && $existing->is_user_overridden) {
            if ($existing->platform && $existing->modpack_id) {
                $this->checkVersion($server, $existing->platform, $existing->modpack_id, 'manual', null);
            }
            return;
        }

        // Skip ignored servers
        if ($existing && $existing->is_ignored) {
            return;
        }

        // Step 1: modpack_installations table (fastest, most reliable)
        // This is a Pterodactyl table — may not exist on all panels
        try {
            $installation = DB::table('modpack_installations')
                ->where('server_id', $server->id)
                ->first();

            if ($installation && !empty($installation->provider) && !empty($installation->modpack_id)) {
                $this->checkVersion($server, $installation->provider, (string) $installation->modpack_id, 'installer', null);
                return;
            }
        } catch (\Exception $e) {
            // Table doesn't exist — skip this detection method
        }

        // Step 2: Egg variables
        $platform = $this->getVariable($server, 'MODPACK_PLATFORM');
        $modpackId = $this->getVariable($server, 'MODPACK_ID');

        if (empty($modpackId) && !empty($platform)) {
            $modpackId = match($platform) {
                'curseforge' => $this->getVariable($server, 'CURSEFORGE_ID'),
                'modrinth' => $this->getVariable($server, 'MODRINTH_PROJECT_ID'),
                'ftb' => $this->getVariable($server, 'FTB_MODPACK_ID'),
                'technic' => $this->getVariable($server, 'TECHNIC_SLUG'),
                default => null
            };
        }

        if (!empty($platform) && !empty($modpackId)) {
            $this->checkVersion($server, $platform, $modpackId, 'egg', null);
            return;
        }

        // Step 3: File-based detection via DaemonFileRepository (last resort)
        $detected = $this->detectFromFiles($server);
        if ($detected) {
            $this->checkVersion($server, $detected['platform'], $detected['modpack_id'], 'file', $detected['installed_version'] ?? null);
            return;
        }

        // Step 4: Nothing found
        $this->warn("  No modpack detected");
        $this->updateDatabase($server, [
            'status' => 'unconfigured',
            'detection_method' => 'unknown',
            'last_checked' => now(),
        ]);
    }

    private function detectFromFiles(Server $server): ?array
    {
        try {
            $this->fileRepository->setServer($server);
        } catch (\Exception $e) {
            $this->line("  [debug] Cannot connect to Wings: " . $e->getMessage());
            return null;
        }

        // CurseForge: manifest.json
        $cf = $this->detectCurseForge($server);
        if ($cf) return $cf;

        // Modrinth: modrinth.index.json
        $mr = $this->detectModrinth($server);
        if ($mr) return $mr;

        // FTB: version.json
        $ftb = $this->detectFtb($server);
        if ($ftb) return $ftb;

        // BCC: logs/latest.log
        $bcc = $this->detectFromBccLog($server);
        if ($bcc) return $bcc;

        return null;
    }

    private function detectCurseForge(Server $server): ?array
    {
        $paths = ['manifest.json', 'minecraftinstance.json'];

        foreach ($paths as $path) {
            try {
                $content = $this->fileRepository->getContent($path);
                $data = json_decode($content, true);

                if (!is_array($data)) continue;

                // Standard manifest.json (CurseForge export)
                if ($path === 'manifest.json' && ($data['manifestType'] ?? '') === 'minecraftModpack') {
                    $projectId = $data['projectID'] ?? null;
                    if ($projectId) {
                        $this->info("  Detected CurseForge via {$path} (projectID: {$projectId})");
                        return [
                            'platform' => 'curseforge',
                            'modpack_id' => (string) $projectId,
                            'name' => $data['name'] ?? null,
                            'installed_version' => $data['version'] ?? null,
                        ];
                    }
                }

                // minecraftinstance.json (CurseForge launcher install)
                if ($path === 'minecraftinstance.json' && isset($data['projectID'])) {
                    $this->info("  Detected CurseForge via {$path} (projectID: {$data['projectID']})");
                    return [
                        'platform' => 'curseforge',
                        'modpack_id' => (string) $data['projectID'],
                        'name' => $data['name'] ?? null,
                        'installed_version' => $data['version'] ?? null,
                    ];
                }
            } catch (\Exception $e) {
                $this->line("  [debug] {$path}: " . $e->getMessage());
            }
        }
        return null;
    }

    private function detectModrinth(Server $server): ?array
    {
        try {
            $content = $this->fileRepository->getContent('modrinth.index.json');
            $data = json_decode($content, true);

            if (is_array($data) && isset($data['formatVersion'])) {
                $slug = isset($data['name'])
                    ? preg_replace('/[^a-z0-9-]/', '', strtolower(str_replace(' ', '-', $data['name'])))
                    : null;
                if ($slug) {
                    $this->info("  Detected Modrinth pack (slug: {$slug})");
                    return [
                        'platform' => 'modrinth',
                        'modpack_id' => $slug,
                        'name' => $data['name'] ?? null,
                    ];
                }
            }
        } catch (\Exception $e) {
            $this->line("  [debug] modrinth.index.json: " . $e->getMessage());
        }
        return null;
    }

    private function detectFtb(Server $server): ?array
    {
        try {
            $content = $this->fileRepository->getContent('version.json');
            $data = json_decode($content, true);

            if (is_array($data) && isset($data['id']) && isset($data['parent'])) {
                $this->info("  Detected FTB pack (id: {$data['parent']})");
                return [
                    'platform' => 'ftb',
                    'modpack_id' => (string) $data['parent'],
                    'name' => $data['name'] ?? null,
                ];
            }
        } catch (\Exception $e) {
            $this->line("  [debug] version.json (FTB): " . $e->getMessage());
        }
        return null;
    }

    private function checkVersion(Server $server, string $platform, string $modpackId, string $method, ?string $installedVersion): void
    {
        try {
            $latestData = $this->apiService->fetchLatestVersion($platform, $modpackId);
            $latestVersion = $latestData['version'] ?? 'Unknown';
            $latestFileId = $latestData['file_id'] ?? null;

            // Get current_version: manifest arg > egg variable > existing DB
            $currentVersion = $installedVersion;
            $currentFileId = null;

            if (empty($currentVersion)) {
                $currentVersion = $this->getVariable($server, 'MODPACK_CURRENT_VERSION');
            }

            $existing = DB::table('modpackchecker_servers')
                ->where('server_uuid', $server->uuid)
                ->first();

            // Only use DB value if file_id is set (validated) OR detection wasn't installer
            if (empty($currentVersion) && $existing) {
                if ($method !== 'installer' || !empty($existing->current_file_id)) {
                    $currentVersion = $existing->current_version ?? null;
                }
            }
            $currentFileId = $existing->current_file_id ?? null;

            // Truth File: read .modpack-checker.json from server filesystem
            if (empty($currentFileId)) {
                try {
                    $this->fileRepository->setServer($server);
                    $truthRaw = $this->fileRepository->getContent('.modpack-checker.json');
                    $truthFile = json_decode($truthRaw, true);
                    if (!empty($truthFile['file_id'])) {
                        $currentFileId = $truthFile['file_id'];
                        $currentVersion = $currentVersion ?: ($truthFile['version'] ?? null);
                        $this->line("  [truth] Read file_id {$currentFileId} from .modpack-checker.json");
                    }
                } catch (\Exception $e) {
                    // Truth File doesn't exist yet
                }
            }

            // Legacy manifest.json: extract fileID, write Truth File
            if (empty($currentFileId)) {
                try {
                    $this->fileRepository->setServer($server);
                    $manifest = json_decode($this->fileRepository->getContent('manifest.json'), true);
                    if (!empty($manifest['files'][0]['fileID'])) {
                        $currentFileId = (string) $manifest['files'][0]['fileID'];
                        $this->writeTruthFile($server, $modpackId, $currentFileId, $manifest['version'] ?? null);
                        $currentVersion = $currentVersion ?: ($manifest['version'] ?? null);
                        $this->line("  [manifest] Extracted file_id {$currentFileId}, wrote Truth File");
                    }
                } catch (\Exception $e) {
                    // No manifest
                }
            }

            // NEVER seed from latest — if unknown, mark pending_calibration
            if (empty($currentVersion) && empty($currentFileId)) {
                $this->updateDatabase($server, [
                    'platform' => $platform,
                    'modpack_id' => $modpackId,
                    'modpack_name' => $latestData['name'],
                    'latest_version' => $latestVersion,
                    'latest_file_id' => $latestFileId,
                    'status' => 'pending_calibration',
                    'detection_method' => $method,
                    'error_message' => null,
                    'last_checked' => now(),
                ]);
                $this->info("  ⏳ PENDING: {$latestData['name']} — calibration required");
                return;
            }

            // Compare: prefer file ID, fall back to string
            if ($currentFileId && $latestFileId) {
                $updateAvailable = $latestFileId !== $currentFileId;
            } else {
                $updateAvailable = $currentVersion !== $latestVersion;
            }

            $this->updateDatabase($server, [
                'platform' => $platform,
                'modpack_id' => $modpackId,
                'modpack_name' => $latestData['name'],
                'current_version' => $currentVersion,
                'latest_version' => $latestVersion,
                'current_file_id' => $currentFileId,
                'latest_file_id' => $latestFileId,
                'status' => $updateAvailable ? 'update_available' : 'up_to_date',
                'detection_method' => $method,
                'error_message' => null,
                'last_checked' => now(),
            ]);

            $icon = $updateAvailable ? '🟠 UPDATE' : '🟢 OK';
            $this->info("  {$icon}: {$latestData['name']} — current: {$currentVersion}, latest: {$latestVersion} [{$method}]");

        } catch (\Exception $e) {
            $this->error("  Error: {$e->getMessage()}");
            $this->updateDatabase($server, [
                'platform' => $platform,
                'modpack_id' => $modpackId,
                'detection_method' => $method,
                'status' => 'error',
                'error_message' => $e->getMessage(),
                'last_checked' => now(),
            ]);
        }
    }

    /**
     * BCC log parsing — detect modpack from BetterCompatibilityChecker output.
     */
    private function detectFromBccLog(Server $server): ?array
    {
        try {
            $log = $this->fileRepository->getContent('logs/latest.log');
            if (preg_match('/Loaded BetterCompatibilityChecker - Modpack: (.+?) \| Version: (.+)/', $log, $m)) {
                $name = trim($m[1]);
                $version = trim($m[2]);
                if ($name !== 'CHANGE_ME' && $version !== 'CHANGE_ME') {
                    $this->info("  Detected via BCC log: {$name} v{$version}");
                    return [
                        'platform' => 'unknown',
                        'modpack_id' => strtolower(str_replace(' ', '-', $name)),
                        'name' => $name,
                        'installed_version' => $version,
                    ];
                }
            }
        } catch (\Exception $e) {
            $this->line("  [debug] BCC log: " . $e->getMessage());
        }
        return null;
    }

    /**
     * Write .modpack-checker.json Truth File to server filesystem.
     */
    private function writeTruthFile(Server $server, string $projectId, string $fileId, ?string $version): void
    {
        try {
            $this->fileRepository->setServer($server);
            $this->fileRepository->putContent('.modpack-checker.json', json_encode([
                'extension' => 'modpackchecker',
                'project_id' => $projectId,
                'file_id' => $fileId,
                'version' => $version,
                'calibrated_at' => now()->toIso8601String(),
            ], JSON_PRETTY_PRINT));
        } catch (\Exception $e) {
            \Log::warning('[MVC] Could not write Truth File: ' . $e->getMessage());
        }
    }

    private function getVariable(Server $server, string $name): ?string
    {
        $variable = $server->variables()
            ->where('env_variable', $name)
            ->first();
        return $variable?->server_value;
    }

    private function updateDatabase(Server $server, array $data): void
    {
        DB::table('modpackchecker_servers')->updateOrInsert(
            ['server_uuid' => $server->uuid],
            array_merge($data, ['updated_at' => now()])
        );
    }
}
