#include "director.hpp"

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

#include <cerrno>
#include <cstdlib>
#include <iostream>

#include "context_escenes.hpp"
#include "core/audio/audio.hpp"
#include "core/audio/audio_cache.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/escenes/escena_joc.hpp"
#include "game/escenes/escena_logo.hpp"
#include "game/escenes/escena_titol.hpp"
#include "game/options.hpp"
#include "project.h"

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

// Using declarations per simplificar el codi
using GestorEscenes::ContextEscenes;
using Escena = ContextEscenes::Escena;

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

    // Inicialitzar opcions amb 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, sense fallback
    std::string pack_path = resource_base + "/resources.pack";
    if (!Resource::Helper::initializeResourceSystem(pack_path, false)) {
        std::cerr << "ERROR FATAL: No es pot carregar " << pack_path << "\n";
        std::cerr << "El joc no pot continuar sense els 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 amb 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 amb 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 fitxer de configuració
    Options::setConfigFile(system_folder_ + "/config.yaml");

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

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

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

    if (Options::console) {
        std::cout << "Configuració 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 opcions
    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ó 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 ja 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 joc
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 amb configuració de Options
    SDLManager sdl(initial_width, initial_height, Options::window.fullscreen);

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

    // Inicialitzar sistema d'audio
    Audio::init();
    Audio::get()->setMusicVolume(1.0);
    Audio::get()->setSoundVolume(0.4);

    // Precachejar música per evitar lag al començar
    AudioCache::getMusic("title.ogg");
    AudioCache::getMusic("game.ogg");
    if (Options::console) {
        std::cout << "Música precachejada: "
                  << AudioCache::getMusicCacheSize() << " fitxers\n";
    }

    // Crear context d'escenes
    ContextEscenes context;
#ifdef _DEBUG
    context.canviar_escena(Escena::TITOL);
#else
    context.canviar_escena(Escena::LOGO);
#endif

    // Bucle principal de gestió d'escenes
    while (context.escena_desti() != Escena::EIXIR) {
        // Sincronitzar GestorEscenes::actual amb context
        // (altres sistemes encara poden llegir GestorEscenes::actual)
        GestorEscenes::actual = context.escena_desti();

        switch (context.escena_desti()) {
            case Escena::LOGO: {
                EscenaLogo logo(sdl, context);
                logo.executar();
                break;
            }

            case Escena::TITOL: {
                EscenaTitol titol(sdl, context);
                titol.executar();
                break;
            }

            case Escena::JOC: {
                EscenaJoc joc(sdl, context);
                joc.executar();
                break;
            }

            default:
                break;
        }
    }

    // Sincronitzar final amb GestorEscenes::actual
    GestorEscenes::actual = Escena::EIXIR;

    return 0;
}
