<?php

/**
 * =============================================================================
 * MODPACK VERSION CHECKER - API CONTROLLER
 * =============================================================================
 * 
 * Part of the ModpackChecker Blueprint extension for Pterodactyl Panel.
 * 
 * PURPOSE:
 * Provides API endpoints for checking modpack versions across multiple platforms
 * (CurseForge, Modrinth, FTB, Technic). Supports both on-demand manual checks
 * from the server console and cached status retrieval for the dashboard badge.
 * 
 * ARCHITECTURE OVERVIEW:
 * This controller serves two distinct use cases:
 * 
 * 1. MANUAL CHECK (manualCheck method)
 *    - Called from: Server console "Check for Updates" button
 *    - Behavior: Makes LIVE API calls via ModpackApiService
 *    - Rate limited: 2 requests per minute per server
 * 
 * 2. DASHBOARD STATUS (getStatus method)
 *    - Called from: Dashboard badge component (UpdateBadge.tsx)
 *    - Behavior: Reads from LOCAL DATABASE CACHE only - NO external API calls
 *    - Why cached? Prevents rate limit hell on panels with many servers
 * 
 * ROUTES (defined in routes/client.php):
 * - POST /api/client/extensions/modpackchecker/servers/{server}/check -> manualCheck()
 * - GET  /api/client/extensions/modpackchecker/status -> getStatus()
 * 
 * @package     Pterodactyl\BlueprintFramework\Extensions\modpackchecker
 * @author      Firefrost Gaming / Frostystyle <dev@firefrostgaming.com>
 * @version     1.0.0
 * @see         ModpackApiService.php (centralized API logic)
 * @see         CheckModpackUpdates.php (cron command that populates the cache)
 * =============================================================================
 */

namespace Pterodactyl\Http\Controllers;

use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Models\Server;
use Pterodactyl\Repositories\Wings\DaemonFileRepository;
use Pterodactyl\Services\ModpackApiService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;

class ModpackAPIController extends Controller
{
    public function __construct(
        private DaemonFileRepository $fileRepository,
        private ModpackApiService $apiService
    ) {}

    /**
     * Manual version check triggered from the server console UI.
     * 
     * Rate limited to 2 requests per minute per server to prevent API abuse.
     * 
     * @param  Request  $request  The incoming HTTP request
     * @param  Server   $server   The server to check
     * @return JsonResponse
     */
    public function manualCheck(Request $request, Server $server): JsonResponse
    {
        // Rate Limiting: Max 2 requests per minute per server
        $limitKey = 'modpack_check_' . $server->uuid;
        if (RateLimiter::tooManyAttempts($limitKey, 2)) {
            $seconds = RateLimiter::availableIn($limitKey);
            return response()->json([
                'success' => false,
                'error' => "Too many requests. Please wait {$seconds} seconds before checking again.",
            ], 429);
        }
        RateLimiter::hit($limitKey, 60);

        // 1. Try Egg Variables first (most reliable)
        $platform = $this->getEggVariable($server, 'MODPACK_PLATFORM');
        $modpackId = $this->getEggVariable($server, 'MODPACK_ID');

        // Also check platform-specific variables
        if (empty($modpackId)) {
            $modpackId = match($platform) {
                'curseforge' => $this->getEggVariable($server, 'CURSEFORGE_ID'),
                'modrinth' => $this->getEggVariable($server, 'MODRINTH_PROJECT_ID'),
                'ftb' => $this->getEggVariable($server, 'FTB_MODPACK_ID'),
                'technic' => $this->getEggVariable($server, 'TECHNIC_SLUG'),
                default => null
            };
        }

        // 2. Check modpack_installations table (Pterodactyl — may not exist)
        if (empty($platform) || empty($modpackId)) {
            try {
                $installation = DB::table('modpack_installations')
                    ->where('server_id', $server->id)
                    ->first();
                if ($installation) {
                    $platform = $platform ?: ($installation->provider ?? null);
                    $modpackId = $modpackId ?: (string) ($installation->modpack_id ?? '');
                }
            } catch (\Exception $e) {
                // Table doesn't exist on this panel — skip
            }
        }

        // 3. If still nothing, try file detection
        if (empty($platform) || empty($modpackId)) {
            $detected = $this->detectFromFiles($server);
            $platform = $platform ?: ($detected['platform'] ?? null);
            $modpackId = $modpackId ?: ($detected['modpack_id'] ?? null);
        }

        // 4. If still nothing, check cached cron data
        if (empty($platform) || empty($modpackId)) {
            $cached = DB::table('modpackchecker_servers')
                ->where('server_uuid', $server->uuid)
                ->first();
            if ($cached && !empty($cached->platform) && !empty($cached->modpack_id)) {
                $platform = $cached->platform;
                $modpackId = $cached->modpack_id;
            }
        }

        // 5. If still nothing, return helpful error
        if (empty($platform) || empty($modpackId)) {
            return response()->json([
                'success' => false,
                'platform' => $platform ?? null,
                'modpack_id' => $modpackId ?? null,
                'error' => 'Could not detect modpack. Set MODPACK_PLATFORM and MODPACK_ID in startup variables.',
            ]);
        }

        // 6. Check the appropriate API using the unified Service
        try {
            $versionData = $this->apiService->fetchLatestVersion($platform, $modpackId);

            // Get cached current_version for comparison
            $cached = DB::table('modpackchecker_servers')
                ->where('server_uuid', $server->uuid)
                ->first();
            $currentVersion = $cached->current_version ?? null;

            return response()->json([
                'success' => true,
                'platform' => $platform,
                'modpack_id' => $modpackId,
                'modpack_name' => $versionData['name'],
                'current_version' => $currentVersion,
                'latest_version' => $versionData['version'],
                'update_available' => $currentVersion && $currentVersion !== $versionData['version'],
                'status' => 'checked',
            ]);
        } catch (\Exception $e) {
            return response()->json([
                'success' => false,
                'platform' => $platform,
                'modpack_id' => $modpackId,
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Retrieve an egg variable value for a specific server.
     * 
     * @param  Server  $server  The server to query
     * @param  string  $name    The environment variable name
     * @return string|null      The variable's value, or null if not set
     */
    private function getEggVariable(Server $server, string $name): ?string
    {
        $variable = $server->variables()
            ->where('env_variable', $name)
            ->first();
        return $variable?->server_value;
    }

    /**
     * Attempt to detect modpack platform and ID by reading server files.
     * 
     * Fallback method when egg variables aren't set.
     * 
     * @param  Server  $server  The server to scan
     * @return array            Contains: platform, modpack_id (all nullable)
     */
    private function detectFromFiles(Server $server): array
    {
        try {
            // Try CurseForge manifest.json
            $manifest = $this->readServerFile($server, 'manifest.json');
            if ($manifest) {
                $data = json_decode($manifest, true);
                if (is_array($data) && isset($data['manifestType']) && $data['manifestType'] === 'minecraftModpack') {
                    return [
                        'platform' => 'curseforge',
                        'modpack_id' => $data['projectID'] ?? null,
                        'name' => $data['name'] ?? null,
                        'version' => $data['version'] ?? null,
                    ];
                }
            }

            // Try Modrinth modrinth.index.json
            $modrinthIndex = $this->readServerFile($server, 'modrinth.index.json');
            if ($modrinthIndex) {
                $data = json_decode($modrinthIndex, true);
                if (is_array($data) && isset($data['formatVersion'])) {
                    // Best-effort slug derivation from pack name for API lookups.
                    // dependencies.minecraft is a MC version (e.g. "1.20.1"), NOT a project ID.
                    // NOTE: This may not match the actual Modrinth slug if the project name
                    // differs from its URL slug. Set MODPACK_ID egg variable for reliability.
                    $slug = isset($data['name']) ? preg_replace('/[^a-z0-9-]/', '', strtolower(str_replace(' ', '-', $data['name']))) : null;
                    return [
                        'platform' => 'modrinth',
                        'modpack_id' => $slug,
                        'name' => $data['name'] ?? null,
                        'version' => $data['versionId'] ?? null,
                    ];
                }
            }
        } catch (\Exception $e) {
            // File detection failed, return empty
        }

        return [];
    }

    /**
     * Read a file from the game server via the Wings daemon.
     * 
     * @param  Server  $server  The server whose files we're reading
     * @param  string  $path    Relative path from server root
     * @return string|null      File contents, or null if unreadable
     */
    private function readServerFile(Server $server, string $path): ?string
    {
        try {
            $this->fileRepository->setServer($server);
            return $this->fileRepository->getContent($path);
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Get cached update status for all of a user's servers.
     * 
     * THIS IS THE DASHBOARD BADGE ENDPOINT.
     * 
     * CRITICAL: This method ONLY reads from the local database cache.
     * It NEVER makes external API calls.
     * 
     * @param  Request  $request  The incoming HTTP request
     * @return JsonResponse       Keyed by server_uuid
     */
    public function getStatus(Request $request): JsonResponse
    {
        $user = $request->user();
        if (!$user) {
            return response()->json([], 401);
        }

        // Get all server UUIDs the user has access to
        // Root admins can see all servers; regular users only see assigned servers
        if (method_exists($user, 'root_admin') ? $user->root_admin : ($user->root_admin ?? false)) {
            $serverUuids = \Pterodactyl\Models\Server::pluck('uuid')->toArray();
        } else {
            $serverUuids = $user->accessibleServers()->pluck('uuid')->toArray();
        }
        
        // Query our cache table for these servers
        $statuses = DB::table('modpackchecker_servers')
            ->whereIn('server_uuid', $serverUuids)
            ->get()
            ->keyBy('server_uuid');
        
        $result = [];
        foreach ($statuses as $uuid => $status) {
            $result[$uuid] = [
                'update_available' => $status->status === 'update_available',
                'modpack_name' => $status->modpack_name,
                'current_version' => $status->current_version,
                'latest_version' => $status->latest_version,
            ];
        }
        
        return response()->json($result);
    }

    /**
     * Get cached status for a single server (zero-click widget).
     * Reads from DB only — no external API calls.
     */
    public function serverStatus(Request $request, Server $server): JsonResponse
    {
        $cached = DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->first();

        if (!$cached || $cached->is_ignored ?? false) {
            return response()->json([
                'configured' => false,
                'is_ignored' => $cached->is_ignored ?? false,
            ]);
        }

        return response()->json([
            'configured' => !empty($cached->platform) && $cached->status !== 'unconfigured',
            'pending_calibration' => $cached->status === 'pending_calibration',
            'platform' => $cached->platform,
            'modpack_name' => $cached->modpack_name,
            'current_version' => $cached->current_version,
            'latest_version' => $cached->latest_version,
            'current_file_id' => $cached->current_file_id ?? null,
            'latest_file_id' => $cached->latest_file_id ?? null,
            'update_available' => $cached->status === 'update_available',
            'last_checked' => $cached->last_checked,
            'detection_method' => $cached->detection_method ?? 'unknown',
            'is_ignored' => $cached->is_ignored ?? false,
        ]);
    }

    /**
     * Get release history for recalibrate dropdown.
     * Makes external API call — rate limited.
     */
    public function releases(Request $request, Server $server): JsonResponse
    {
        $limitKey = 'modpack_releases_' . $server->uuid;
        if (RateLimiter::tooManyAttempts($limitKey, 2)) {
            $seconds = RateLimiter::availableIn($limitKey);
            return response()->json(['error' => "Too many requests. Wait {$seconds}s."], 429);
        }
        RateLimiter::hit($limitKey, 60);

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

        if (!$cached || empty($cached->platform) || empty($cached->modpack_id)) {
            return response()->json(['releases' => []]);
        }

        $releases = $this->apiService->fetchFileHistory($cached->platform, $cached->modpack_id, 10);

        return response()->json(['releases' => $releases]);
    }

    /**
     * Recalibrate current version (user selects from release list).
     * Sets is_user_overridden = true.
     */
    public function calibrate(Request $request, Server $server): JsonResponse
    {
        $fileId = $request->input('file_id');
        $version = $request->input('version');

        if (empty($version)) {
            return response()->json(['error' => 'version is required'], 400);
        }

        $updated = DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->update([
                'current_version' => $version,
                'current_file_id' => $fileId,
                'is_user_overridden' => true,
                'updated_at' => now(),
            ]);

        if (!$updated) {
            return response()->json(['error' => 'Server not found in modpack cache'], 404);
        }

        // Re-evaluate update status
        $cached = DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->first();

        $updateAvailable = false;
        if ($cached->latest_file_id && $fileId) {
            $updateAvailable = $cached->latest_file_id !== $fileId;
        } else {
            $updateAvailable = $cached->latest_version !== $version;
        }

        DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->update([
                'status' => $updateAvailable ? 'update_available' : 'up_to_date',
            ]);

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

        return response()->json([
            'success' => true,
            'current_version' => $version,
            'update_available' => $updateAvailable,
        ]);
    }

    /**
     * Toggle is_ignored flag for a server.
     */
    public function toggleIgnore(Request $request, Server $server): JsonResponse
    {
        $existing = DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->first();

        if (!$existing) {
            // Create a record to track the ignore
            DB::table('modpackchecker_servers')->insert([
                'server_uuid' => $server->uuid,
                'is_ignored' => true,
                'status' => 'ignored',
                'updated_at' => now(),
            ]);
            return response()->json(['is_ignored' => true]);
        }

        $newState = !($existing->is_ignored ?? false);
        DB::table('modpackchecker_servers')
            ->where('server_uuid', $server->uuid)
            ->update([
                'is_ignored' => $newState,
                'updated_at' => now(),
            ]);

        return response()->json(['is_ignored' => $newState]);
    }
}
