#include "core/system/director.hpp"

#include <SDL3/SDL.h>
#include <sys/stat.h>  // Para mkdir, stat, S_IRWXU
#include <unistd.h>    // Para getuid

#include <cerrno>    // Para errno, EEXIST, EACCES, ENAMETOO...
#include <cstdio>    // Para printf, perror
#include <cstdlib>   // Para exit, EXIT_FAILURE, srand
#include <iostream>  // Para basic_ostream, operator<<, cout
#include <memory>    // Para make_unique, unique_ptr
#include <string>    // Para operator+, allocator, char_traits

#include "core/audio/audio.hpp"                // Para Audio
#include "core/input/input.hpp"                // Para Input, InputAction
#include "core/locale/locale.hpp"              // Para Locale
#include "core/rendering/render_info.hpp"      // Para RenderInfo
#include "core/rendering/screen.hpp"           // Para Screen
#include "core/resources/resource_cache.hpp"   // Para Resource
#include "core/resources/resource_helper.hpp"  // Para ResourceHelper
#include "core/resources/resource_list.hpp"    // Para Asset, AssetType
#include "core/resources/resource_loader.hpp"  // Para ResourceLoader
#include "game/gameplay/cheevos.hpp"           // Para Cheevos
#include "game/options.hpp"                    // Para Options, options, OptionsVideo
#include "game/scene_manager.hpp"              // Para SceneManager
#include "game/scenes/boot_loader.hpp"         // Para BootLoader
#include "game/scenes/credits.hpp"             // Para Credits
#include "game/scenes/ending.hpp"              // Para Ending
#include "game/scenes/ending2.hpp"             // Para Ending2
#include "game/scenes/game.hpp"                // Para Game, GameMode
#include "game/scenes/game_over.hpp"           // Para GameOver
#include "game/scenes/loading_screen.hpp"      // Para LoadingScreen
#include "game/scenes/logo.hpp"                // Para Logo
#include "game/scenes/title.hpp"               // Para Title
#include "game/ui/console.hpp"                 // Para Console
#include "game/ui/notifier.hpp"                // Para Notifier
#include "utils/defines.hpp"                   // Para WINDOW_CAPTION

#ifdef _DEBUG
#include "core/system/debug.hpp"       // Para Debug
#include "game/editor/map_editor.hpp"  // Para MapEditor
#endif

#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#include <pwd.h>
#endif

namespace {
    auto getExecutablePath() -> std::string {
#ifdef __EMSCRIPTEN__
        // En Emscripten els assets estan al root del filesystem virtual (/data, /config)
        return "";
#else
        std::string base = SDL_GetBasePath();
        if (!base.empty() && base.back() == '/') {
            base.pop_back();
        }
        return base;
#endif
    }
}  // namespace

// Constructor
Director::Director()
    : executable_path_(getExecutablePath()) {
    std::cout << "Game start" << '\n';

    // Crea la carpeta del sistema donde guardar datos
    createSystemFolder("jailgames");
    createSystemFolder("jailgames/jaildoctors_dilemma");

    // Crea el subdirectorio shaders/ dentro de system_folder_ sin modificar system_folder_
    {
        std::string shaders_dir = system_folder_ + "/shaders";
        struct stat st = {.st_dev = 0};
        if (stat(shaders_dir.c_str(), &st) == -1) {
            errno = 0;
#ifdef _WIN32
            mkdir(shaders_dir.c_str());
#else
            mkdir(shaders_dir.c_str(), S_IRWXU);
#endif
        }
    }

    // Determinar el prefijo de ruta según la plataforma
#ifdef MACOS_BUNDLE
    const std::string PREFIX = "/../Resources";
#else
    const std::string PREFIX;
#endif

    // Preparar ruta al pack (en macOS bundle está en Contents/Resources/)
    std::string pack_path = executable_path_ + PREFIX + "/resources.pack";

#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
    // ============================================================
    // RELEASE BUILD: Pack-first architecture
    // ============================================================
    std::cout << "\n** RELEASE MODE: Pack-first initialization\n";

    // 1. Initialize resource pack system (required, no fallback)
    std::cout << "Initializing resource pack: " << pack_path << '\n';
    if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
        std::cerr << "ERROR: Failed to load resources.pack (required in release builds)\n";
        exit(EXIT_FAILURE);
    }

    // 2. Validate pack integrity
    std::cout << "Validating pack integrity..." << '\n';
    if (!Resource::Loader::get().validatePack()) {
        std::cerr << "ERROR: Pack validation failed\n";
        exit(EXIT_FAILURE);
    }

    // 3. Load assets.yaml from pack
    std::cout << "Loading assets configuration from pack..." << '\n';
    std::string assets_config = Resource::Loader::get().loadAssetsConfig();
    if (assets_config.empty()) {
        std::cerr << "ERROR: Failed to load assets.yaml from pack\n";
        exit(EXIT_FAILURE);
    }

    // 4. Initialize Asset system with config from pack
    // NOTE: In release, don't use executable_path or PREFIX - paths in pack are relative
    // Pass empty string to avoid issues when running from different directories
    Resource::List::init("");                                                  // Empty executable_path in release
    Resource::List::get()->loadFromString(assets_config, "", system_folder_);  // Empty PREFIX for pack
    std::cout << "Asset system initialized from pack\n";

#else
    // ============================================================
    // DEVELOPMENT BUILD: Filesystem-first architecture
    // ============================================================
    std::cout << "\n** DEVELOPMENT MODE: Filesystem-first initialization\n";

    // 1. Initialize Asset system from filesystem
    Resource::List::init(executable_path_);

    // 2. Load asset configuration from disk
    // Note: Asset verification happens during Resource::Cache::load()
    setFileList();

    // 3. Initialize resource pack system (optional, with fallback)
    std::cout << "Initializing resource pack (development mode): " << pack_path << '\n';
    Resource::Helper::initializeResourceSystem(pack_path, true);

#endif

    // Configura la ruta y carga las opciones desde un fichero
    Options::setConfigFile(Resource::List::get()->get("config.yaml"));  // NOLINT(readability-static-accessed-through-instance)
    Options::loadFromFile();

#ifdef __EMSCRIPTEN__
    // A la versió web el navegador gestiona la finestra: forcem zoom x3
    // perquè la textura 256x192 no es vegi minúscula al canvas HTML,
    // i desactivem el borde per aprofitar al màxim l'espai del canvas.
    Options::video.fullscreen = false;
    Options::video.integer_scale = true;
    Options::window.zoom = 4;
    Options::video.border.enabled = true;
    Options::video.border.height = 8;
    Options::video.border.width = 8;
#endif

    // Configura la ruta y carga los presets de PostFX
    Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));  // NOLINT(readability-static-accessed-through-instance)
    Options::loadPostFXFromFile();

    // Configura la ruta y carga los presets del shader CrtPi
    Options::setCrtPiFile(Resource::List::get()->get("crtpi.yaml"));  // NOLINT(readability-static-accessed-through-instance)
    Options::loadCrtPiFromFile();

    // En mode quiosc, forçar pantalla completa independentment de la configuració
    if (Options::kiosk.enabled) {
        Options::video.fullscreen = true;
    }

    // Inicializa JailAudio
    Audio::init();

    // Crea los objetos
    Screen::init();

    // Inicializa el singleton del cache sin disparar la carga. La carga real
    // la hace Director::iterate() llamando a Cache::loadStep() en cada frame,
    // de forma que la ventana, los eventos y la barra de progreso están vivos
    // desde el primer tick.
    Resource::Cache::init();
    Resource::Cache::get()->beginLoad();

    // Special handling for gamecontrollerdb.txt - SDL needs filesystem path
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
    // In release, construct the path manually (not from Asset which has empty executable_path)
    std::string gamecontroller_db = executable_path_ + PREFIX + "/gamecontrollerdb.txt";
    Input::init(gamecontroller_db);
#else
    // In development, use Asset as normal
    Input::init(Resource::List::get()->get("gamecontrollerdb.txt"));  // NOLINT(readability-static-accessed-through-instance) Carga configuración de controles
#endif

    // Aplica las teclas y botones del gamepad configurados desde Options
    Input::get()->applyKeyboardBindingsFromOptions();
    Input::get()->applyGamepadBindingsFromOptions();

    std::cout << "\n";  // Fin de inicialización mínima de sistemas

    // Construeix l'escena inicial (BootLoader). finishBoot() la canviarà a
    // LOGO (o la que digui Debug) quan Cache::loadStep() complete la càrrega.
    SceneManager::current = SceneManager::Scene::BOOT_LOADER;
    switchToActiveScene();
}

// Inicialitzacions que depenen del cache poblat. Es crida des d'iterate()
// just quan Cache::loadStep() retorna true, amb la finestra i el bucle ja vius.
void Director::finishBoot() {
    Notifier::init("", "8bithud");
    RenderInfo::init();
    Console::init("8bithud");
    Screen::get()->setNotificationsEnabled(true);

#ifdef _DEBUG
    Debug::init();
#ifdef __EMSCRIPTEN__
    // A wasm el debug.yaml viu a SYSTEM_FOLDER (MEMFS no persistent) i no està
    // disponible. Saltem el loadFromFile i entrem directament a la GAME.
    SceneManager::current = SceneManager::Scene::GAME;
#else
    Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
    Debug::get()->loadFromFile();
    SceneManager::current = Debug::get()->getInitialScene();
#endif
    MapEditor::init();
#else
    // En release, pasamos a LOGO siempre tras la carga
    SceneManager::current = SceneManager::Scene::LOGO;
#endif

    // Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
    {
        // En release el locale está en el pack, no en el filesystem
        std::string locale_key = Resource::List::get()->get(Options::language + ".yaml");  // NOLINT(readability-static-accessed-through-instance)
        auto locale_bytes = Resource::Helper::loadFile(locale_key);
        std::string locale_content(locale_bytes.begin(), locale_bytes.end());
        Locale::initFromContent(locale_content);
    }
#else
    Locale::init(Resource::List::get()->get(Options::language + ".yaml"));  // NOLINT(readability-static-accessed-through-instance)
#endif

    // Special handling for cheevos.bin - also needs filesystem path
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
    std::string cheevos_path = system_folder_ + "/cheevos.bin";
    Cheevos::init(cheevos_path);
#else
    Cheevos::init(Resource::List::get()->get("cheevos.bin"));
#endif
}

Director::~Director() {
    // Guarda las opciones a un fichero
    Options::saveToFile();

    // Destruir l'escena activa ABANS dels singletons. Si no, el unique_ptr membre
    // destrueix l'escena al final del destructor — quan Audio, Screen, Resource...
    // ja són morts — i qualsevol accés en els seus destructors és un UAF.
    active_scene_.reset();

    // Destruye los singletones
    Cheevos::destroy();
    Locale::destroy();
#ifdef _DEBUG
    MapEditor::destroy();
    Debug::destroy();
#endif
    Input::destroy();
    Console::destroy();
    RenderInfo::destroy();
    Notifier::destroy();
    Resource::Cache::destroy();
    Resource::Helper::shutdownResourceSystem();  // Shutdown resource pack system
    Audio::destroy();
    Screen::destroy();
    Resource::List::destroy();

    SDL_Quit();

    std::cout << "\nBye!" << '\n';
}

// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string& folder) {  // NOLINT(readability-convert-member-functions-to-static)
#ifdef __EMSCRIPTEN__
    // En Emscripten utilitzem MEMFS (no persistent entre sessions).
    // No cal crear directoris: MEMFS els crea automàticament en escriure-hi.
    system_folder_ = "/config/" + folder;
    return;
#else
#ifdef _WIN32
    system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
    struct passwd* pw = getpwuid(getuid());
    const char* homedir = pw->pw_dir;
    system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
    struct passwd* pw = getpwuid(getuid());
    const char* homedir = pw->pw_dir;
    system_folder_ = std::string(homedir) + "/.config/" + folder;

    {
        // Intenta crear ".config", per si no existeix
        std::string config_base_folder = std::string(homedir) + "/.config";
        int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
        if (ret == -1 && errno != EEXIST) {
            printf("ERROR CREATING CONFIG BASE FOLDER.");
            exit(EXIT_FAILURE);
        }
    }
#endif

    struct stat st = {.st_dev = 0};
    if (stat(system_folder_.c_str(), &st) == -1) {
        errno = 0;
#ifdef _WIN32
        int ret = mkdir(system_folder_.c_str());
#else
        int ret = mkdir(system_folder_.c_str(), S_IRWXU);
#endif

        if (ret == -1) {
            switch (errno) {
                case EACCES:
                    printf("the parent directory does not allow write");
                    exit(EXIT_FAILURE);

                case EEXIST:
                    printf("pathname already exists");
                    exit(EXIT_FAILURE);

                case ENAMETOOLONG:
                    printf("pathname is too long");
                    exit(EXIT_FAILURE);

                default:
                    perror("mkdir");
                    exit(EXIT_FAILURE);
            }
        }
    }
#endif  // __EMSCRIPTEN__
}

// Carga la configuración de assets desde assets.yaml
void Director::setFileList() {  // NOLINT(readability-convert-member-functions-to-static)
    // Determinar el prefijo de ruta según la plataforma
#ifdef MACOS_BUNDLE
    const std::string PREFIX = "/../Resources";
#else
    const std::string PREFIX;
#endif

    // Construir ruta al archivo de configuración de assets
    std::string config_path = executable_path_ + PREFIX + "/config/assets.yaml";

    // Cargar todos los assets desde el archivo de configuración
    // La verificación de existencia de archivos se realiza durante Resource::Cache::load()
    Resource::List::get()->loadFromFile(config_path, PREFIX, system_folder_);
}

// Construeix l'escena segons SceneManager::current i la deixa en active_scene_.
// Substitueix els vells runLogo(), runTitle(), runGame(), etc.
void Director::switchToActiveScene() {
    // Si la escena anterior va demanar RESTART_CURRENT, restaurem la que estava activa
    if (SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
        SceneManager::current = SceneManager::scene_before_restart;
    }

    // Destrueix l'escena anterior (pot parar música, etc. al seu destructor)
    active_scene_.reset();

    switch (SceneManager::current) {
        case SceneManager::Scene::BOOT_LOADER:
            active_scene_ = std::make_unique<BootLoader>();
            break;

        case SceneManager::Scene::LOGO:
            active_scene_ = std::make_unique<Logo>();
            break;

        case SceneManager::Scene::LOADING_SCREEN:
            active_scene_ = std::make_unique<LoadingScreen>();
            break;

        case SceneManager::Scene::TITLE:
            active_scene_ = std::make_unique<Title>();
            break;

        case SceneManager::Scene::CREDITS:
            active_scene_ = std::make_unique<Credits>();
            break;

        case SceneManager::Scene::DEMO:
            active_scene_ = std::make_unique<Game>(Game::Mode::DEMO);
            break;

        case SceneManager::Scene::GAME:
            Audio::get()->stopMusic();
            active_scene_ = std::make_unique<Game>(Game::Mode::GAME);
            break;

        case SceneManager::Scene::GAME_OVER:
            active_scene_ = std::make_unique<GameOver>();
            break;

        case SceneManager::Scene::ENDING:
            active_scene_ = std::make_unique<Ending>();
            break;

        case SceneManager::Scene::ENDING2:
            active_scene_ = std::make_unique<Ending2>();
            break;

        default:
            break;
    }

    current_scene_ = SceneManager::current;
}

// SDL_AppIterate: executa un frame de l'escena activa
auto Director::iterate() -> SDL_AppResult {
    if (SceneManager::current == SceneManager::Scene::QUIT) {
        return SDL_APP_SUCCESS;
    }

    // Fase de boot: anem cridant loadStep() fins que el cache estiga ple.
    // Durant aquesta fase l'escena activa és BootLoader (una barra de progrés).
    if (boot_loading_) {
        try {
            // Budget de 50ms: durant el boot el joc va a ~15-20 FPS, suficient
            // per veure la barra avançar suau i processar events del WM/ESC,
            // i evita el 50% d'ineficiència que provocaria un budget < vsync.
            if (Resource::Cache::get()->loadStep(50 /*ms*/)) {
                if (Options::loading.show && Options::loading.wait_for_input) {
                    boot_waiting_for_input_ = true;  // Esperar tecla antes de continuar
                } else {
                    finishBoot();
                }
                boot_loading_ = false;
                // finishBoot() ja ha fixat SceneManager::current a LOGO (o la que
                // digui Debug). El canvi d'escena es fa just a sota.
            }
        } catch (const std::exception& e) {
            std::cerr << "Fatal error during resource load: " << e.what() << '\n';
            SceneManager::current = SceneManager::Scene::QUIT;
            return SDL_APP_FAILURE;
        }
    }

    // Si l'escena ha canviat (o s'ha demanat RESTART_CURRENT), canviar-la abans del frame
    if (SceneManager::current != current_scene_ || SceneManager::current == SceneManager::Scene::RESTART_CURRENT) {
        switchToActiveScene();
    }

    if (active_scene_) {
        active_scene_->iterate();
    }

    return SDL_APP_CONTINUE;
}

// SDL_AppEvent: despatxa un event a l'escena activa
auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
#ifndef __EMSCRIPTEN__
    // A la versió web no tenim event de quit del navegador
    if (event.type == SDL_EVENT_QUIT) {
        SceneManager::current = SceneManager::Scene::QUIT;
        return SDL_APP_SUCCESS;
    }
#endif

    // Si estamos esperando input tras la carga: consumir tecla/botón y arrancar
    if (boot_waiting_for_input_) {
        const bool IS_KEY = event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat;
        const bool IS_BUTTON = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN;
        if (IS_KEY || IS_BUTTON) {
            boot_waiting_for_input_ = false;
            finishBoot();
        }
        return SDL_APP_CONTINUE;
    }

    if (active_scene_) {
        active_scene_->handleEvent(event);
    }

    return SDL_APP_CONTINUE;
}