#include "director.hpp"

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

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

#include "core/audio/audio.hpp"
#include "core/audio/audio_adapter.hpp"
#include "core/defaults/audio.hpp"
#include "core/defaults/window.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/system/notifier.hpp"
#include "core/utils/path_utils.hpp"
#include "debug_overlay.hpp"
#include "game/config_yaml.hpp"
#include "game/scenes/game_scene.hpp"
#include "game/scenes/logo_scene.hpp"
#include "game/scenes/title_scene.hpp"
#include "global_events.hpp"
#include "project.h"
#include "scene.hpp"
#include "scene_context.hpp"

#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(int argc, char* argv[])
    : cfg_(&ConfigYaml::engine_config) {
    std::cout << "Game start\n";

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

    // Convertir arguments a std::vector<std::string> i comprovar-los
    std::vector<std::string> args(argv, argv + argc);
    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
    ConfigYaml::setConfigFile(system_folder_ + "/config.yaml");

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

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

    // Aplicar configuración de controls dels jugadors
    Input::get()->applyPlayer1Bindings(cfg_->player1);
    Input::get()->applyPlayer2Bindings(cfg_->player2);

    if (cfg_->console) {
        std::cout << "Configuración carregada\n";
        std::cout << "  Finestra: " << cfg_->window.width << "×"
                  << cfg_->window.height << '\n';
        std::cout << "  Input: " << Input::get()->getNumGamepads()
                  << " gamepad(s) detectat(s)\n";
    }

    std::cout << '\n';

    // === Bootstrap de finestra, audio i subsistemes de runtime ===

    int initial_width = static_cast<int>(std::round(
        Defaults::Window::WIDTH * cfg_->window.zoom_factor));
    int initial_height = static_cast<int>(std::round(
        Defaults::Window::HEIGHT * cfg_->window.zoom_factor));

    sdl_ = std::make_unique<SDLManager>(initial_width, initial_height, cfg_->window.fullscreen, *cfg_, [] { ConfigYaml::saveToFile(); });

    // CRÍTIC: forçar ocultació del cursor DESPRÉS d'inicialitzar SDL,
    // perquè la creació de la finestra el reactiva.
    if (!cfg_->window.fullscreen) {
        Mouse::forceHide();
    }

    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);

    AudioResource::getMusic("title.ogg");
    AudioResource::getMusic("game.ogg");
    if (cfg_->console) {
        std::cout << "Música precacheada\n";
    }

    context_ = std::make_unique<SceneContext>();
#ifdef _DEBUG
    context_->setNextScene(SceneType::TITLE);
#else
    context_->setNextScene(SceneType::LOGO);
#endif

    debug_overlay_ = std::make_unique<System::DebugOverlay>(
        sdl_->getRenderer(),
        cfg_->rendering);

    System::Notifier::init(sdl_->getRenderer());

    last_ticks_ms_ = SDL_GetTicks();
}

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

    // Destruir subsistemes en ordre invers a la construcció. El Notifier
    // referencia el renderer, així que ha de morir abans que sdl_.
    // SDL_Quit() el crida SDL automàticament després de SDL_AppQuit; no
    // l'hem de cridar nosaltres.
    current_scene_.reset();
    debug_overlay_.reset();
    System::Notifier::destroy();
    context_.reset();
    sdl_.reset();

    Input::destroy();
    Audio::destroy();

    std::cout << "\nBye!\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") {
            cfg_->console = true;
            std::cout << "Mode consola activat\n";
        } else if (argument == "--reset-config") {
            ConfigYaml::init();
            ConfigYaml::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. Zero-init de toda la struct stat
    // para evitar -Wmissing-field-initializers (struct con muchos campos
    // específicos del SO que no queremos enumerar manualmente).
    struct stat st{};
    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 (cfg_->console) {
        std::cout << "Carpeta del sistema: " << system_folder_ << '\n';
    }
}

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;
    }
}

auto Director::advanceScene() -> SDL_AppResult {
    current_scene_.reset();
    const SceneType NEXT = context_->nextScene();
    if (NEXT == SceneType::EXIT) {
        SceneManager::actual = SceneType::EXIT;
        return SDL_APP_SUCCESS;
    }
    SceneManager::actual = NEXT;
    current_scene_ = buildScene(NEXT, *sdl_, *context_);
    if (!current_scene_) {
        SceneManager::actual = SceneType::EXIT;
        return SDL_APP_SUCCESS;
    }
    return SDL_APP_CONTINUE;
}

auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
    // 1. Window events (resize, minimize, focus...)
    if (sdl_->handleWindowEvent(event)) {
        return SDL_APP_CONTINUE;
    }

    // 2. Events globals (F1-F6, ESC, QUIT, gamepad hotplug).
    // GlobalEvents marca context_->nextScene() = EXIT en ESC doble o QUIT;
    // activem la bandera per fer-ho fluir cap a SDL_APP_SUCCESS al pròxim tick.
    if (GlobalEvents::handle(event, *sdl_, *context_)) {
        if (context_->nextScene() == SceneType::EXIT) {
            wants_quit_ = true;
        }
        return SDL_APP_CONTINUE;
    }

    // 3. F11 → toggle del debug overlay (cas especial fora de GlobalEvents).
    if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_F11) {
        debug_overlay_->toggle();
        return SDL_APP_CONTINUE;
    }

    // 4. Esdeveniment específic de l'escena actual.
    if (current_scene_) {
        current_scene_->handleEvent(event);
    }
    return SDL_APP_CONTINUE;
}

auto Director::iterate() -> SDL_AppResult {
    if (wants_quit_) {
        return SDL_APP_SUCCESS;
    }

    // Pivotar a la següent escena si l'actual ha acabat (o és la primera).
    if (!current_scene_ || current_scene_->isFinished()) {
        SDL_AppResult pivot = advanceScene();
        if (pivot != SDL_APP_CONTINUE) {
            return pivot;
        }
    }

    // Delta time real, capeado a 50ms per evitar grans salts.
    const Uint64 NOW = SDL_GetTicks();
    float delta_time = static_cast<float>(NOW - last_ticks_ms_) / 1000.0F;
    last_ticks_ms_ = NOW;
    delta_time = std::min(delta_time, 0.05F);

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

    current_scene_->update(delta_time);
    debug_overlay_->update(delta_time);
    if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
        notifier->update(delta_time);
    }
    Audio::update();

    // Si la swapchain no està disponible (finestra minimitzada, etc.),
    // saltar-se draw+present aquest frame.
    if (!sdl_->clear(0, 0, 0)) {
        return SDL_APP_CONTINUE;
    }
    sdl_->updateRenderingContext();
    current_scene_->draw();
    debug_overlay_->draw();  // sempre per damunt de l'escena
    if (const auto* notifier = System::Notifier::get(); notifier != nullptr) {
        notifier->draw();  // toast: per damunt de tot
    }
    sdl_->present();
    return SDL_APP_CONTINUE;
}
