#include "director.hpp"

#include <SDL3/SDL.h>
#include <sys/stat.h>

#include <algorithm>
#include <cerrno>
#include <cstdlib>
#include <iostream>
#include <memory>

#include "debug_overlay.hpp"
#include "scene.hpp"
#include "scene_context.hpp"
#include "global_events.hpp"
#include "core/audio/audio.hpp"
#include "core/audio/audio_adapter.hpp"
#include "core/defaults.hpp"
#include "core/input/input.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/resources/resource_helper.hpp"
#include "core/resources/resource_loader.hpp"
#include "core/utils/path_utils.hpp"
#include "game/scenes/game_scene.hpp"
#include "game/scenes/logo_scene.hpp"
#include "game/scenes/title_scene.hpp"
#include "game/options.hpp"
#include "project.h"

#ifndef _WIN32
#include <pwd.h>
#include <unistd.h>
#endif

// Using declarations per simplificar el codi
using SceneManager::SceneContext;
using SceneType = SceneContext::SceneType;

// Constructor
Director::Director(std::vector<std::string> const& args) {
    std::cout << "Orni Attack - Inici\n";

    // Inicialitzar opciones con valors per defecte
    Options::init();

    // Comprovar arguments del programa
    executable_path_ = checkProgramArguments(args);

    // Inicialitzar sistema de rutes
    Utils::initializePathSystem(args[0].c_str());

    // Obtenir ruta base dels recursos
    std::string resource_base = Utils::getResourceBasePath();

    // Inicialitzar sistema de recursos
#ifdef RELEASE_BUILD
    // Mode release: paquet obligatori, sin fallback
    std::string pack_path = resource_base + "/resources.pack";
    if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
        std::cerr << "ERROR FATAL: No es pot load " << pack_path << "\n";
        std::cerr << "El juego no pot continuar sin los recursos.\n";
        std::exit(1);
    }

    // Validar integritat del paquet
    if (!Resource::Loader::get().validatePack()) {
        std::cerr << "ERROR FATAL: El paquet de recursos está corromput\n";
        std::exit(1);
    }

    std::cout << "Sistema de recursos inicialitzat (mode release)\n";
#else
    // Mode desenvolupament: intentar paquet con fallback a data/
    std::string pack_path = resource_base + "/resources.pack";
    Resource::Helper::initializeResourceSystem(pack_path, true);

    if (Resource::Helper::isPackLoaded()) {
        std::cout << "Sistema de recursos inicialitzat (mode dev con paquet)\n";
    } else {
        std::cout << "Sistema de recursos inicialitzat (mode dev, fallback a data/)\n";
    }

    // Establir ruta base per al fallback
    Resource::Loader::get().setBasePath(resource_base);
#endif

    // Crear carpetes del sistema
    createSystemFolder("jailgames");
    createSystemFolder(std::string("jailgames/") + Project::NAME);

    // Establir ruta del file de configuración
    Options::setConfigFile(system_folder_ + "/config.yaml");

    // Carregar o crear configuración
    Options::loadFromFile();

    // Inicialitzar sistema de input
    Input::init("data/gamecontrollerdb.txt");

    // Aplicar configuración de controls dels jugadors
    Input::get()->applyPlayer1BindingsFromOptions();
    Input::get()->applyPlayer2BindingsFromOptions();

    if (Options::console) {
        std::cout << "Configuración carregada\n";
        std::cout << "  Finestra: " << Options::window.width << "×"
                  << Options::window.height << '\n';
        std::cout << "  Física: rotation=" << Options::physics.rotation_speed
                  << " rad/s\n";
        std::cout << "  Input: " << Input::get()->getNumGamepads()
                  << " gamepad(s) detectat(s)\n";
    }

    std::cout << '\n';
}

Director::~Director() {
    // Guardar opciones
    Options::saveToFile();

    // Cleanup input
    Input::destroy();

    // Cleanup audio
    Audio::destroy();

    // Cleanup SDL
    SDL_Quit();

    std::cout << "\nAdéu!\n";
}

// Comprovar arguments del programa
auto Director::checkProgramArguments(std::vector<std::string> const& args)
    -> std::string {
    for (std::size_t i = 1; i < args.size(); ++i) {
        const std::string& argument = args[i];

        if (argument == "--console") {
            Options::console = true;
            std::cout << "Mode consola activat\n";
        } else if (argument == "--reset-config") {
            Options::init();
            Options::saveToFile();
            std::cout << "Configuración restablida als valors per defecte\n";
        }
    }

    return args[0];  // Retornar ruta de l'executable
}

// Crear carpeta del sistema (específic per plataforma)
void Director::createSystemFolder(const std::string& folder) {
#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;

    // CRÍTIC: Crear ~/.config 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: No es pot crear la carpeta ~/.config\n");
            exit(EXIT_FAILURE);
        }
    }
#endif

    // Comprovar si la carpeta existeix
    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("ERROR: Permisos denegats creant %s\n", system_folder_.c_str());
                    exit(EXIT_FAILURE);

                case EEXIST:
                    // La carpeta ya existeix (race condition), continuar
                    break;

                case ENAMETOOLONG:
                    printf("ERROR: Ruta massa llarga: %s\n", system_folder_.c_str());
                    exit(EXIT_FAILURE);

                default:
                    perror("mkdir");
                    exit(EXIT_FAILURE);
            }
        }
    }

    if (Options::console) {
        std::cout << "Carpeta del sistema: " << system_folder_ << '\n';
    }
}

// Bucle principal del juego
auto Director::run() -> int {
    // Calculate initial size from saved zoom_factor
    int initial_width = static_cast<int>(std::round(
        Defaults::Window::WIDTH * Options::window.zoom_factor));
    int initial_height = static_cast<int>(std::round(
        Defaults::Window::HEIGHT * Options::window.zoom_factor));

    // Crear gestor SDL con configuración de Options
    SDLManager sdl(initial_width, initial_height, Options::window.fullscreen);

    // CRÍTIC: Forçar ocultació del cursor DESPRÉS de toda la inicialización SDL
    // Això evita que SDL mostre el cursor automàticament durante la creació de la finestra
    if (!Options::window.fullscreen) {
        Mouse::forceHide();
    }

    // Inicializar sistema de audio (config inyectada desde Defaults)
    const Audio::Config AUDIO_CONFIG{
        .enabled = Defaults::Audio::ENABLED,
        .volume = Defaults::Audio::VOLUME,
        .music_enabled = Defaults::Audio::MUSIC_ENABLED,
        .music_volume = Defaults::Audio::MUSIC_VOLUME,
        .sound_enabled = Defaults::Audio::SOUND_ENABLED,
        .sound_volume = Defaults::Audio::SOUND_VOLUME,
    };
    Audio::init(AUDIO_CONFIG);
    Audio::get()->applySettings(AUDIO_CONFIG);  // Aplicar volúmenes iniciales al motor

    // Precachear música para evitar lag al empezar
    AudioResource::getMusic("title.ogg");
    AudioResource::getMusic("game.ogg");
    if (Options::console) {
        std::cout << "Música precacheada\n";
    }

    // Crear context de escenes
    SceneContext context;
#ifdef _DEBUG
    context.setNextScene(SceneType::TITLE);
#else
    context.setNextScene(SceneType::LOGO);
#endif

    // Overlay de debug (FPS + VSync). Vive en el Director porque es global
    // a todas las escenas. Toggle con F11 (visible por defecto en _DEBUG).
    System::DebugOverlay debug_overlay(sdl.getRenderer());

    // Bucle principal: construir escena → frame loop → destruir → siguiente.
    while (context.nextScene() != SceneType::EXIT) {
        SceneManager::actual = context.nextScene();
        std::unique_ptr<Scene> scene = buildScene(context.nextScene(), sdl, context);
        if (!scene) {
            break;
        }
        runFrameLoop(*scene, sdl, context, debug_overlay);
    }

    SceneManager::actual = SceneType::EXIT;
    return 0;
}

auto Director::buildScene(SceneType type, SDLManager& sdl, SceneContext& context)
    -> std::unique_ptr<Scene> {
    switch (type) {
        case SceneType::LOGO:
            return std::make_unique<LogoScene>(sdl, context);
        case SceneType::TITLE:
            return std::make_unique<TitleScene>(sdl, context);
        case SceneType::GAME:
            return std::make_unique<GameScene>(sdl, context);
        case SceneType::EXIT:
        default:
            return nullptr;
    }
}

void Director::runFrameLoop(Scene& scene, SDLManager& sdl, SceneContext& context,
                            System::DebugOverlay& debug_overlay) {
    SDL_Event event;
    Uint64 last_time = SDL_GetTicks();

    while (!scene.isFinished()) {
        // Delta time real, capeado a 50ms para evitar grandes saltos.
        const Uint64 NOW = SDL_GetTicks();
        float delta_time = static_cast<float>(NOW - last_time) / 1000.0F;
        last_time = NOW;
        delta_time = std::min(delta_time, 0.05F);

        Mouse::updateCursorVisibility();
        Input::get()->update();

        // Event loop: primero ventana, después globales, después F11
        // (toggle del overlay), después escena.
        while (SDL_PollEvent(&event)) {
            if (sdl.handleWindowEvent(event)) {
                continue;
            }
            if (GlobalEvents::handle(event, sdl, context)) {
                continue;
            }
            if (event.type == SDL_EVENT_KEY_DOWN
                && event.key.scancode == SDL_SCANCODE_F11) {
                debug_overlay.toggle();
                continue;
            }
            scene.handleEvent(event);
        }

        scene.update(delta_time);
        debug_overlay.update(delta_time);
        Audio::update();

        sdl.clear(0, 0, 0);
        sdl.updateRenderingContext();
        scene.draw();
        debug_overlay.draw();  // siempre on top de la escena
        sdl.present();
    }
}
