#include "config_yaml.hpp"

#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>

#include "core/defaults/rendering.hpp"
#include "core/defaults/window.hpp"
#include "external/fkyaml_node.hpp"
#include "project.h"

namespace ConfigYaml {

    namespace {
        // Aliases internes per a la implementació, no exposades al .hpp.
        // Permeten escriure window.width en lloc d'engine_config.window.width.
        Config::WindowConfig& window = engine_config.window;
        Config::RenderingConfig& rendering = engine_config.rendering;
        Config::PlayerBindings& player1 = engine_config.player1;
        Config::PlayerBindings& player2 = engine_config.player2;
        bool& console = engine_config.console;
    }  // namespace

    // ========== FUNCIONS AUXILIARS PER CONVERSIÓ DE CONTROLES ==========

    // Mapa de SDL_Scancode a string
    static const std::unordered_map<SDL_Scancode, std::string> SCANCODE_TO_STRING = {
        {SDL_SCANCODE_A, "A"},
        {SDL_SCANCODE_B, "B"},
        {SDL_SCANCODE_C, "C"},
        {SDL_SCANCODE_D, "D"},
        {SDL_SCANCODE_E, "E"},
        {SDL_SCANCODE_F, "F"},
        {SDL_SCANCODE_G, "G"},
        {SDL_SCANCODE_H, "H"},
        {SDL_SCANCODE_I, "I"},
        {SDL_SCANCODE_J, "J"},
        {SDL_SCANCODE_K, "K"},
        {SDL_SCANCODE_L, "L"},
        {SDL_SCANCODE_M, "M"},
        {SDL_SCANCODE_N, "N"},
        {SDL_SCANCODE_O, "O"},
        {SDL_SCANCODE_P, "P"},
        {SDL_SCANCODE_Q, "Q"},
        {SDL_SCANCODE_R, "R"},
        {SDL_SCANCODE_S, "S"},
        {SDL_SCANCODE_T, "T"},
        {SDL_SCANCODE_U, "U"},
        {SDL_SCANCODE_V, "V"},
        {SDL_SCANCODE_W, "W"},
        {SDL_SCANCODE_X, "X"},
        {SDL_SCANCODE_Y, "Y"},
        {SDL_SCANCODE_Z, "Z"},
        {SDL_SCANCODE_1, "1"},
        {SDL_SCANCODE_2, "2"},
        {SDL_SCANCODE_3, "3"},
        {SDL_SCANCODE_4, "4"},
        {SDL_SCANCODE_5, "5"},
        {SDL_SCANCODE_6, "6"},
        {SDL_SCANCODE_7, "7"},
        {SDL_SCANCODE_8, "8"},
        {SDL_SCANCODE_9, "9"},
        {SDL_SCANCODE_0, "0"},
        {SDL_SCANCODE_RETURN, "RETURN"},
        {SDL_SCANCODE_ESCAPE, "ESCAPE"},
        {SDL_SCANCODE_BACKSPACE, "BACKSPACE"},
        {SDL_SCANCODE_TAB, "TAB"},
        {SDL_SCANCODE_SPACE, "SPACE"},
        {SDL_SCANCODE_UP, "UP"},
        {SDL_SCANCODE_DOWN, "DOWN"},
        {SDL_SCANCODE_LEFT, "LEFT"},
        {SDL_SCANCODE_RIGHT, "RIGHT"},
        {SDL_SCANCODE_LSHIFT, "LSHIFT"},
        {SDL_SCANCODE_RSHIFT, "RSHIFT"},
        {SDL_SCANCODE_LCTRL, "LCTRL"},
        {SDL_SCANCODE_RCTRL, "RCTRL"},
        {SDL_SCANCODE_LALT, "LALT"},
        {SDL_SCANCODE_RALT, "RALT"}};

    // Mapa invers: string a SDL_Scancode
    static const std::unordered_map<std::string, SDL_Scancode> STRING_TO_SCANCODE = {
        {"A", SDL_SCANCODE_A},
        {"B", SDL_SCANCODE_B},
        {"C", SDL_SCANCODE_C},
        {"D", SDL_SCANCODE_D},
        {"E", SDL_SCANCODE_E},
        {"F", SDL_SCANCODE_F},
        {"G", SDL_SCANCODE_G},
        {"H", SDL_SCANCODE_H},
        {"I", SDL_SCANCODE_I},
        {"J", SDL_SCANCODE_J},
        {"K", SDL_SCANCODE_K},
        {"L", SDL_SCANCODE_L},
        {"M", SDL_SCANCODE_M},
        {"N", SDL_SCANCODE_N},
        {"O", SDL_SCANCODE_O},
        {"P", SDL_SCANCODE_P},
        {"Q", SDL_SCANCODE_Q},
        {"R", SDL_SCANCODE_R},
        {"S", SDL_SCANCODE_S},
        {"T", SDL_SCANCODE_T},
        {"U", SDL_SCANCODE_U},
        {"V", SDL_SCANCODE_V},
        {"W", SDL_SCANCODE_W},
        {"X", SDL_SCANCODE_X},
        {"Y", SDL_SCANCODE_Y},
        {"Z", SDL_SCANCODE_Z},
        {"1", SDL_SCANCODE_1},
        {"2", SDL_SCANCODE_2},
        {"3", SDL_SCANCODE_3},
        {"4", SDL_SCANCODE_4},
        {"5", SDL_SCANCODE_5},
        {"6", SDL_SCANCODE_6},
        {"7", SDL_SCANCODE_7},
        {"8", SDL_SCANCODE_8},
        {"9", SDL_SCANCODE_9},
        {"0", SDL_SCANCODE_0},
        {"RETURN", SDL_SCANCODE_RETURN},
        {"ESCAPE", SDL_SCANCODE_ESCAPE},
        {"BACKSPACE", SDL_SCANCODE_BACKSPACE},
        {"TAB", SDL_SCANCODE_TAB},
        {"SPACE", SDL_SCANCODE_SPACE},
        {"UP", SDL_SCANCODE_UP},
        {"DOWN", SDL_SCANCODE_DOWN},
        {"LEFT", SDL_SCANCODE_LEFT},
        {"RIGHT", SDL_SCANCODE_RIGHT},
        {"LSHIFT", SDL_SCANCODE_LSHIFT},
        {"RSHIFT", SDL_SCANCODE_RSHIFT},
        {"LCTRL", SDL_SCANCODE_LCTRL},
        {"RCTRL", SDL_SCANCODE_RCTRL},
        {"LALT", SDL_SCANCODE_LALT},
        {"RALT", SDL_SCANCODE_RALT}};

    // Mapa de botó de gamepad (int) a string
    static const std::unordered_map<int, std::string> BUTTON_TO_STRING = {
        {SDL_GAMEPAD_BUTTON_SOUTH, "SOUTH"},  // A (Xbox), Cross (PS)
        {SDL_GAMEPAD_BUTTON_EAST, "EAST"},    // B (Xbox), Circle (PS)
        {SDL_GAMEPAD_BUTTON_WEST, "WEST"},    // X (Xbox), Square (PS)
        {SDL_GAMEPAD_BUTTON_NORTH, "NORTH"},  // Y (Xbox), Triangle (PS)
        {SDL_GAMEPAD_BUTTON_BACK, "BACK"},
        {SDL_GAMEPAD_BUTTON_START, "START"},
        {SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, "LEFT_SHOULDER"},
        {SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, "RIGHT_SHOULDER"},
        {SDL_GAMEPAD_BUTTON_DPAD_UP, "DPAD_UP"},
        {SDL_GAMEPAD_BUTTON_DPAD_DOWN, "DPAD_DOWN"},
        {SDL_GAMEPAD_BUTTON_DPAD_LEFT, "DPAD_LEFT"},
        {SDL_GAMEPAD_BUTTON_DPAD_RIGHT, "DPAD_RIGHT"},
        {100, "L2_AS_BUTTON"},  // Trigger L2 como a botó digital
        {101, "R2_AS_BUTTON"}   // Trigger R2 como a botó digital
    };

    // Mapa invers: string a botó de gamepad
    static const std::unordered_map<std::string, int> STRING_TO_BUTTON = {
        {"SOUTH", SDL_GAMEPAD_BUTTON_SOUTH},
        {"EAST", SDL_GAMEPAD_BUTTON_EAST},
        {"WEST", SDL_GAMEPAD_BUTTON_WEST},
        {"NORTH", SDL_GAMEPAD_BUTTON_NORTH},
        {"BACK", SDL_GAMEPAD_BUTTON_BACK},
        {"START", SDL_GAMEPAD_BUTTON_START},
        {"LEFT_SHOULDER", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
        {"RIGHT_SHOULDER", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
        {"DPAD_UP", SDL_GAMEPAD_BUTTON_DPAD_UP},
        {"DPAD_DOWN", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
        {"DPAD_LEFT", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
        {"DPAD_RIGHT", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
        {"L2_AS_BUTTON", 100},
        {"R2_AS_BUTTON", 101}};

    static auto scancodeToString(SDL_Scancode code) -> std::string {
        auto it = SCANCODE_TO_STRING.find(code);
        return (it != SCANCODE_TO_STRING.end()) ? it->second : "UNKNOWN";
    }

    static auto stringToScancode(const std::string& str) -> SDL_Scancode {
        auto it = STRING_TO_SCANCODE.find(str);
        return (it != STRING_TO_SCANCODE.end()) ? it->second : SDL_SCANCODE_UNKNOWN;
    }

    static auto buttonToString(int button) -> std::string {
        auto it = BUTTON_TO_STRING.find(button);
        return (it != BUTTON_TO_STRING.end()) ? it->second : "UNKNOWN";
    }

    static auto stringToButton(const std::string& str) -> int {
        auto it = STRING_TO_BUTTON.find(str);
        return (it != STRING_TO_BUTTON.end()) ? it->second : SDL_GAMEPAD_BUTTON_INVALID;
    }

    // ========== FI FUNCIONS AUXILIARS ==========

    // Inicialitzar opciones con valors per defecte de Defaults::
    void init() {
#ifdef _DEBUG
        console = true;
#else
        console = false;
#endif

        // Window
        window.width = Defaults::Window::WIDTH;
        window.height = Defaults::Window::HEIGHT;
        window.fullscreen = Defaults::Window::FULLSCREEN;
        window.zoom_factor = Defaults::Window::BASE_ZOOM;

        // Rendering
        rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
        rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
        rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;

        // Version
        version = std::string(Project::VERSION);
    }

    // Establir la ruta del file de configuración
    void setConfigFile(const std::string& path) { config_file_path = path; }

    // Funciones auxiliars per load seccions del YAML

    // Lee un campo escalar del YAML aplicando un validador; si la clau no
    // existe, deja `dest` intacto; si la conversió o la validació fallen,
    // asigna `fallback`. Estàtic per quedar dins de la unitat de traducció.
    template <typename T, typename Validator>
    static void readField(const fkyaml::node& parent, const char* key, T& dest, T fallback, Validator&& validate) {
        if (!parent.contains(key)) {
            return;
        }
        try {
            auto val = parent[key].template get_value<T>();
            dest = validate(val) ? val : fallback;
        } catch (...) {
            dest = fallback;
        }
    }

    // Variant sin validador: només lectura amb fallback en cas d'error.
    template <typename T>
    static void readField(const fkyaml::node& parent, const char* key, T& dest, T fallback) {
        if (!parent.contains(key)) {
            return;
        }
        try {
            dest = parent[key].template get_value<T>();
        } catch (...) {
            dest = fallback;
        }
    }

    static void loadWindowConfigFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("window")) {
            return;
        }
        const auto& win = yaml["window"];

        readField(win, "width", window.width, Defaults::Window::WIDTH, [](int v) { return v >= Defaults::Window::MIN_WIDTH; });
        readField(win, "height", window.height, Defaults::Window::HEIGHT, [](int v) { return v >= Defaults::Window::MIN_HEIGHT; });
        readField(win, "fullscreen", window.fullscreen, false);

        if (win.contains("zoom_factor")) {
            readField(win, "zoom_factor", window.zoom_factor, Defaults::Window::BASE_ZOOM, [](float v) { return v >= Defaults::Window::MIN_ZOOM && v <= 10.0F; });
        } else {
            // Legacy config: infer zoom from width
            window.zoom_factor = static_cast<float>(window.width) / Defaults::Window::WIDTH;
            window.zoom_factor = std::max(Defaults::Window::MIN_ZOOM, window.zoom_factor);
        }
    }

    static void loadRenderingConfigFromYaml(const fkyaml::node& yaml) {
        if (yaml.contains("rendering")) {
            const auto& rend = yaml["rendering"];

            if (rend.contains("vsync")) {
                try {
                    int val = rend["vsync"].get_value<int>();
                    // Validar: solo 0 o 1
                    rendering.vsync = (val == 0 || val == 1) ? val : Defaults::Rendering::VSYNC_DEFAULT;
                } catch (...) {
                    rendering.vsync = Defaults::Rendering::VSYNC_DEFAULT;
                }
            }

            // Resolució de render: validem el parell (w, h) contra la llista
            // tancada de presets 16:9. Si falla l'una o l'altra, fem fallback
            // dels dos camps al default per mantenir un parell vàlid.
            int candidate_w = rendering.render_width;
            int candidate_h = rendering.render_height;
            readField(rend, "render_width", candidate_w, Defaults::Rendering::RENDER_WIDTH_DEFAULT);
            readField(rend, "render_height", candidate_h, Defaults::Rendering::RENDER_HEIGHT_DEFAULT);
            if (Defaults::Rendering::isValidRenderResolution(candidate_w, candidate_h)) {
                rendering.render_width = candidate_w;
                rendering.render_height = candidate_h;
            } else {
                if (console) {
                    std::cerr << "Resolució de render invàlida al YAML ("
                              << candidate_w << "x" << candidate_h
                              << "), fallback a "
                              << Defaults::Rendering::RENDER_WIDTH_DEFAULT << "x"
                              << Defaults::Rendering::RENDER_HEIGHT_DEFAULT << '\n';
                }
                rendering.render_width = Defaults::Rendering::RENDER_WIDTH_DEFAULT;
                rendering.render_height = Defaults::Rendering::RENDER_HEIGHT_DEFAULT;
            }
        }
    }

    // Carregar controls del player 1 desde YAML
    static void loadPlayer1ControlsFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("player1")) {
            return;
        }

        const auto& p1 = yaml["player1"];

        // Carregar controls de teclat
        if (p1.contains("keyboard")) {
            const auto& kb = p1["keyboard"];
            if (kb.contains("key_left")) {
                player1.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
            }
            if (kb.contains("key_right")) {
                player1.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
            }
            if (kb.contains("key_thrust")) {
                player1.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
            }
            if (kb.contains("key_shoot")) {
                player1.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
            }
        }

        // Carregar controls de gamepad
        if (p1.contains("gamepad")) {
            const auto& gp = p1["gamepad"];
            if (gp.contains("button_left")) {
                player1.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
            }
            if (gp.contains("button_right")) {
                player1.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
            }
            if (gp.contains("button_thrust")) {
                player1.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
            }
            if (gp.contains("button_shoot")) {
                player1.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
            }
        }

        // Carregar nom del gamepad
        if (p1.contains("gamepad_name")) {
            player1.gamepad_name = p1["gamepad_name"].get_value<std::string>();
        }
    }

    // Carregar controls del player 2 desde YAML
    static void loadPlayer2ControlsFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("player2")) {
            return;
        }

        const auto& p2 = yaml["player2"];

        // Carregar controls de teclat
        if (p2.contains("keyboard")) {
            const auto& kb = p2["keyboard"];
            if (kb.contains("key_left")) {
                player2.keyboard.key_left = stringToScancode(kb["key_left"].get_value<std::string>());
            }
            if (kb.contains("key_right")) {
                player2.keyboard.key_right = stringToScancode(kb["key_right"].get_value<std::string>());
            }
            if (kb.contains("key_thrust")) {
                player2.keyboard.key_thrust = stringToScancode(kb["key_thrust"].get_value<std::string>());
            }
            if (kb.contains("key_shoot")) {
                player2.keyboard.key_shoot = stringToScancode(kb["key_shoot"].get_value<std::string>());
            }
        }

        // Carregar controls de gamepad
        if (p2.contains("gamepad")) {
            const auto& gp = p2["gamepad"];
            if (gp.contains("button_left")) {
                player2.gamepad.button_left = stringToButton(gp["button_left"].get_value<std::string>());
            }
            if (gp.contains("button_right")) {
                player2.gamepad.button_right = stringToButton(gp["button_right"].get_value<std::string>());
            }
            if (gp.contains("button_thrust")) {
                player2.gamepad.button_thrust = stringToButton(gp["button_thrust"].get_value<std::string>());
            }
            if (gp.contains("button_shoot")) {
                player2.gamepad.button_shoot = stringToButton(gp["button_shoot"].get_value<std::string>());
            }
        }

        // Carregar nom del gamepad
        if (p2.contains("gamepad_name")) {
            player2.gamepad_name = p2["gamepad_name"].get_value<std::string>();
        }
    }

    // Carregar configuración des del file YAML
    auto loadFromFile() -> bool {
        const std::string CONFIG_VERSION = std::string(Project::VERSION);

        std::ifstream file(config_file_path);
        if (!file.good()) {
            // El file no existeix → crear-ne un de nuevo con valors per defecte
            if (console) {
                std::cout << "Archivo de config no trobat, creant-ne un de nuevo: "
                          << config_file_path << '\n';
            }
            saveToFile();
            return true;
        }

        // Llegir todo el contingut del file
        std::string content((std::istreambuf_iterator<char>(file)),
            std::istreambuf_iterator<char>());
        file.close();

        try {
            // Parsejar YAML
            auto yaml = fkyaml::node::deserialize(content);

            // Validar versión
            if (yaml.contains("version")) {
                version = yaml["version"].get_value<std::string>();
            }

            if (CONFIG_VERSION != version) {
                // Versión incompatible → regenerar config
                if (console) {
                    std::cout << "Versión de config incompatible (esperada: "
                              << CONFIG_VERSION << ", trobada: " << version
                              << "), regenerant config\n";
                }
                init();
                saveToFile();
                return true;
            }

            // Carregar seccions
            loadWindowConfigFromYaml(yaml);
            loadRenderingConfigFromYaml(yaml);
            loadPlayer1ControlsFromYaml(yaml);
            loadPlayer2ControlsFromYaml(yaml);

            if (console) {
                std::cout << "Config carregada correctament desde: " << config_file_path
                          << '\n';
            }

            return true;

        } catch (const fkyaml::exception& e) {
            // Error de parsejat YAML → regenerar config
            if (console) {
                std::cerr << "Error parsejant YAML: " << e.what() << '\n';
                std::cerr << "Creant config nuevo con valors per defecte\n";
            }
            init();
            saveToFile();
            return true;
        }
    }

    // Guardar controls del player 1 a YAML
    static void savePlayer1ControlsToYaml(std::ofstream& file) {
        file << "# CONTROLS JUGADOR 1\n";
        file << "player1:\n";
        file << "  keyboard:\n";
        file << "    key_left: " << scancodeToString(player1.keyboard.key_left) << "\n";
        file << "    key_right: " << scancodeToString(player1.keyboard.key_right) << "\n";
        file << "    key_thrust: " << scancodeToString(player1.keyboard.key_thrust) << "\n";
        file << "    key_shoot: " << scancodeToString(player1.keyboard.key_shoot) << "\n";
        file << "  gamepad:\n";
        file << "    button_left: " << buttonToString(player1.gamepad.button_left) << "\n";
        file << "    button_right: " << buttonToString(player1.gamepad.button_right) << "\n";
        file << "    button_thrust: " << buttonToString(player1.gamepad.button_thrust) << "\n";
        file << "    button_shoot: " << buttonToString(player1.gamepad.button_shoot) << "\n";
        file << "  gamepad_name: \"" << player1.gamepad_name << "\"   # Buit = primer disponible\n\n";
    }

    // Guardar controls del player 2 a YAML
    static void savePlayer2ControlsToYaml(std::ofstream& file) {
        file << "# CONTROLS JUGADOR 2\n";
        file << "player2:\n";
        file << "  keyboard:\n";
        file << "    key_left: " << scancodeToString(player2.keyboard.key_left) << "\n";
        file << "    key_right: " << scancodeToString(player2.keyboard.key_right) << "\n";
        file << "    key_thrust: " << scancodeToString(player2.keyboard.key_thrust) << "\n";
        file << "    key_shoot: " << scancodeToString(player2.keyboard.key_shoot) << "\n";
        file << "  gamepad:\n";
        file << "    button_left: " << buttonToString(player2.gamepad.button_left) << "\n";
        file << "    button_right: " << buttonToString(player2.gamepad.button_right) << "\n";
        file << "    button_thrust: " << buttonToString(player2.gamepad.button_thrust) << "\n";
        file << "    button_shoot: " << buttonToString(player2.gamepad.button_shoot) << "\n";
        file << "  gamepad_name: \"" << player2.gamepad_name << "\"   # Buit = segon disponible\n\n";
    }

    // Guardar configuración al file YAML
    auto saveToFile() -> bool {
        std::ofstream file(config_file_path);
        if (!file.is_open()) {
            if (console) {
                std::cerr << "No s'ha pogut obrir el file de config per escriure: "
                          << config_file_path << '\n';
            }
            return false;
        }

        // Escriure manualment per controlar format i comentaris
        file << "# Orni Attack - Archivo de Configuración\n";
        file << "# Auto-generat. Les edicions manuals es preserven si són "
                "vàlides.\n\n";

        file << "version: \"" << Project::VERSION << "\"\n\n";

        file << "# FINESTRA\n";
        file << "window:\n";
        file << "  width: " << window.width << "   # Calculated from zoom_factor\n";
        file << "  height: " << window.height << "   # Calculated from zoom_factor\n";
        file << "  fullscreen: " << (window.fullscreen ? "true" : "false") << "\n";
        file << "  zoom_factor: " << window.zoom_factor << "   # 0.5x-max (0.1 increments)\n\n";

        file << "# RENDERITZACIÓ\n";
        file << "rendering:\n";
        file << "  vsync: " << rendering.vsync << "   # 0=disabled, 1=enabled\n";
        file << "  render_width: " << rendering.render_width
             << "    # Presets 16:9: 1280, 1600, 1920, 2560, 3840 (fallback 1280)\n";
        file << "  render_height: " << rendering.render_height
             << "    # Parell amb render_width (720, 900, 1080, 1440, 2160)\n\n";

        // Guardar controls de jugadors
        savePlayer1ControlsToYaml(file);
        savePlayer2ControlsToYaml(file);

        file.close();

        if (console) {
            std::cout << "Config guardada a: " << config_file_path << '\n';
        }

        return true;
    }

}  // namespace ConfigYaml
