#include "core/resources/resource_list.hpp"

#include <SDL3/SDL.h>  // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError

#include <algorithm>   // Para sort
#include <cstddef>     // Para size_t
#include <exception>   // Para exception
#include <filesystem>  // Para exists, path
#include <fstream>     // Para ifstream, istringstream
#include <iostream>    // Para cout
#include <sstream>     // Para istringstream
#include <stdexcept>   // Para runtime_error

#include "external/fkyaml_node.hpp"  // Para parsear YAML
#include "utils/utils.hpp"           // Para getFileName, printWithDots

namespace Resource {

    namespace {
        // Un item del format modern: pot ser string (path) o mapping ({path, required?, absolute?})
        void parseAssetItem(List& list, const fkyaml::node& item, List::Type type, const std::string& prefix, const std::string& system_folder, const std::string& category, const std::string& type_str) {
            try {
                if (item.is_string()) {
                    auto path = List::replaceVariables(item.get_value<std::string>(), prefix, system_folder);
                    list.add(path, type, true, false);
                    return;
                }
                if (item.is_mapping() && item.contains("path")) {
                    auto path = List::replaceVariables(item["path"].get_value<std::string>(), prefix, system_folder);
                    const bool REQUIRED = !item.contains("required") || item["required"].get_value<bool>();
                    const bool ABSOLUTE = item.contains("absolute") && item["absolute"].get_value<bool>();
                    list.add(path, type, REQUIRED, ABSOLUTE);
                    return;
                }
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: Invalid item in type '%s', category '%s', skipping", type_str.c_str(), category.c_str());
            } catch (const std::exception& e) {
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error parsing asset in category '%s', type '%s': %s", category.c_str(), type_str.c_str(), e.what());
            }
        }

        // (TIPO: [items...]) del format modern. Itera els items i delega a parseAssetItem.
        void parseModernType(List& list, const fkyaml::node& items_node, List::Type type, const std::string& type_str, const std::string& category, const std::string& prefix, const std::string& system_folder) {
            if (!items_node.is_sequence()) {
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: Type '%s' in category '%s' is not a sequence, skipping", type_str.c_str(), category.c_str());
                return;
            }
            for (const auto& item : items_node) {
                parseAssetItem(list, item, type, prefix, system_folder, category, type_str);
            }
        }

        // {type, path, required?, absolute?} del format antic
        void parseLegacyAsset(List& list, const fkyaml::node& asset, const std::string& category, const std::string& prefix, const std::string& system_folder) {
            try {
                if (!asset.contains("type") || !asset.contains("path")) {
                    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: Asset in category '%s' missing 'type' or 'path', skipping", category.c_str());
                    return;
                }
                auto type_str = asset["type"].get_value<std::string>();
                auto path = asset["path"].get_value<std::string>();
                const bool REQUIRED = !asset.contains("required") || asset["required"].get_value<bool>();
                const bool ABSOLUTE = asset.contains("absolute") && asset["absolute"].get_value<bool>();
                path = List::replaceVariables(path, prefix, system_folder);
                list.add(path, List::parseAssetType(type_str), REQUIRED, ABSOLUTE);
            } catch (const std::exception& e) {
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error parsing asset in category '%s': %s", category.c_str(), e.what());
            }
        }

        // Categoria amb format modern (TIPO → [items]). Itera els tipus.
        void parseModernCategory(List& list, const fkyaml::node& category_assets, const std::string& category, const std::string& prefix, const std::string& system_folder) {
            for (auto type_it = category_assets.begin(); type_it != category_assets.end(); ++type_it) {
                try {
                    auto type_str = type_it.key().get_value<std::string>();
                    const List::Type TYPE = List::parseAssetType(type_str);
                    parseModernType(list, type_it.value(), TYPE, type_str, category, prefix, system_folder);
                } catch (const std::exception& e) {
                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error parsing type in category '%s': %s", category.c_str(), e.what());
                }
            }
        }
    }  // namespace

    // Singleton
    List* List::instance = nullptr;

    void List::init(const std::string& executable_path) {
        List::instance = new List(executable_path);
    }

    void List::destroy() {
        delete List::instance;
    }

    auto List::get() -> List* {
        return List::instance;
    }

    // Añade un elemento al mapa (función auxiliar)
    void List::addToMap(const std::string& file_path, Type type, bool required, bool absolute) {
        std::string full_path = absolute ? file_path : executable_path_ + file_path;
        std::string filename = getFileName(full_path);

        // Verificar si ya existe el archivo
        if (file_list_.contains(filename)) {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                "Warning: Asset '%s' already exists, overwriting",
                filename.c_str());
        }

        file_list_.emplace(filename, Item{std::move(full_path), type, required});
    }

    // Añade un elemento a la lista
    void List::add(const std::string& file_path, Type type, bool required, bool absolute) {
        addToMap(file_path, type, required, absolute);
    }

    // Añade un asset al mapa y lo persiste en assets.yaml
    void List::addAsset(const std::string& path, Type type) {
        // Añadir al mapa en memoria
        addToMap(path, type, true, true);

        // Persistir en assets.yaml
        if (config_file_path_.empty()) { return; }

        std::ifstream in(config_file_path_);
        if (!in.is_open()) { return; }
        std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
        in.close();

        // Construir la ruta con variable ${PREFIX} (invertir la sustitución)
        std::string var_path = path;
        if (!executable_path_.empty()) {
            std::string full_prefix = executable_path_ + prefix_;
            auto pos = var_path.find(full_prefix);
            if (pos != std::string::npos) {
                var_path.replace(pos, full_prefix.length(), "${PREFIX}");
            }
        }

        // Buscar la última entrada con el mismo prefijo de ruta e insertar después
        auto last_pos = content.rfind(var_path.substr(0, var_path.rfind('/')));
        if (last_pos != std::string::npos) {
            auto end_of_line = content.find('\n', last_pos);
            if (end_of_line != std::string::npos) {
                std::string entry = "      - " + var_path + "\n";
                content.insert(end_of_line + 1, entry);
            }
        }

        std::ofstream out(config_file_path_);
        if (out.is_open()) {
            out << content;
            out.close();
        }
    }

    // Quita un asset del mapa y lo elimina de assets.yaml
    void List::removeAsset(const std::string& filename) {
        // Obtener la ruta antes de borrar del mapa
        auto it = file_list_.find(filename);
        std::string file_path;
        if (it != file_list_.end()) {
            file_path = it->second.file;
            file_list_.erase(it);
        }

        // Persistir en assets.yaml
        if (config_file_path_.empty() || file_path.empty()) { return; }

        std::ifstream in(config_file_path_);
        if (!in.is_open()) { return; }
        std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
        in.close();

        // Construir la ruta con variable ${PREFIX}
        std::string var_path = file_path;
        if (!executable_path_.empty()) {
            std::string full_prefix = executable_path_ + prefix_;
            auto pos = var_path.find(full_prefix);
            if (pos != std::string::npos) {
                var_path.replace(pos, full_prefix.length(), "${PREFIX}");
            }
        }

        // Buscar la línea con el path y eliminarla
        auto pos = content.find(var_path);
        if (pos != std::string::npos) {
            auto line_start = content.rfind('\n', pos);
            line_start = (line_start == std::string::npos) ? 0 : line_start;
            auto line_end = content.find('\n', pos);
            if (line_end != std::string::npos) {
                content.erase(line_start, line_end - line_start);
            }
        }

        std::ofstream out(config_file_path_);
        if (out.is_open()) {
            out << content;
            out.close();
        }
    }

    // Carga recursos desde un archivo de configuración con soporte para variables
    void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
        config_file_path_ = config_file_path;
        prefix_ = prefix;

        std::ifstream file(config_file_path);
        if (!file.is_open()) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                "Error: Cannot open config file: %s",
                config_file_path.c_str());
            return;
        }

        // Read entire file into string
        std::stringstream buffer;
        buffer << file.rdbuf();
        file.close();

        // Parse using loadFromString
        loadFromString(buffer.str(), prefix, system_folder);
    }

    // Carga recursos desde un string de configuración (para release con pack)
    void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) {
        try {
            auto yaml = fkyaml::node::deserialize(config_content);
            if (!yaml.contains("assets")) {
                SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Invalid assets.yaml format - missing 'assets' key");
                return;
            }
            const auto& assets = yaml["assets"];

            // Iterar sobre cada categoría (fonts, palettes, etc.)
            for (auto it = assets.begin(); it != assets.end(); ++it) {
                const std::string& category = it.key().get_value<std::string>();
                const auto& category_assets = it.value();

                if (category_assets.is_mapping()) {
                    parseModernCategory(*this, category_assets, category, prefix, system_folder);
                } else if (category_assets.is_sequence()) {
                    for (const auto& asset : category_assets) { parseLegacyAsset(*this, asset, category, prefix, system_folder); }
                } else {
                    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: Category '%s' has invalid format, skipping", category.c_str());
                }
            }

            std::cout << "Loaded " << file_list_.size() << " assets from YAML config" << '\n';
        } catch (const fkyaml::exception& e) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "YAML parsing error: %s", e.what());
        } catch (const std::exception& e) {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error loading assets: %s", e.what());
        }
    }

    // Devuelve la ruta completa a un fichero (búsqueda O(1))
    auto List::get(const std::string& filename) const -> std::string {
        auto it = file_list_.find(filename);
        if (it != file_list_.end()) {
            return it->second.file;
        }

        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found", filename.c_str());
        return "";
    }

    // Carga datos del archivo
    auto List::loadData(const std::string& filename) const -> std::vector<uint8_t> {
        auto it = file_list_.find(filename);
        if (it != file_list_.end()) {
            std::ifstream file(it->second.file, std::ios::binary);
            if (!file.is_open()) {
                SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
                    "Warning: Could not open file %s for data loading",
                    filename.c_str());
                return {};
            }

            // Obtener tamaño del archivo
            file.seekg(0, std::ios::end);
            size_t size = file.tellg();
            file.seekg(0, std::ios::beg);

            // Leer datos
            std::vector<uint8_t> data(size);
            file.read(reinterpret_cast<char*>(data.data()), size);
            file.close();

            return data;
        }

        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found for data loading", filename.c_str());
        return {};
    }

    // Verifica si un recurso existe
    auto List::exists(const std::string& filename) const -> bool {
        return file_list_.contains(filename);
    }

    // Parsea string a Type
    auto List::parseAssetType(const std::string& type_str) -> Type {
        if (type_str == "DATA") {
            return Type::DATA;
        }
        if (type_str == "BITMAP") {
            return Type::BITMAP;
        }
        if (type_str == "ANIMATION") {
            return Type::ANIMATION;
        }
        if (type_str == "MUSIC") {
            return Type::MUSIC;
        }
        if (type_str == "SOUND") {
            return Type::SOUND;
        }
        if (type_str == "FONT") {
            return Type::FONT;
        }
        if (type_str == "ROOM") {
            return Type::ROOM;
        }
        if (type_str == "TILEMAP") {
            // TILEMAP está obsoleto, ahora todo es ROOM (.yaml unificado)
            return Type::ROOM;
        }
        if (type_str == "PALETTE") {
            return Type::PALETTE;
        }

        throw std::runtime_error("Unknown asset type: " + type_str);
    }

    // Devuelve el nombre del tipo de recurso
    auto List::getTypeName(Type type) -> std::string {
        switch (type) {
            case Type::DATA:
                return "DATA";
            case Type::BITMAP:
                return "BITMAP";
            case Type::ANIMATION:
                return "ANIMATION";
            case Type::MUSIC:
                return "MUSIC";
            case Type::SOUND:
                return "SOUND";
            case Type::FONT:
                return "FONT";
            case Type::ROOM:
                return "ROOM";
            case Type::PALETTE:
                return "PALETTE";
            default:
                return "ERROR";
        }
    }

    // Devuelve la lista de recursos de un tipo
    auto List::getListByType(Type type) const -> std::vector<std::string> {
        std::vector<std::string> list;

        for (const auto& [filename, item] : file_list_) {
            if (item.type == type) {
                list.push_back(item.file);
            }
        }

        // Ordenar alfabéticamente para garantizar orden consistente
        std::ranges::sort(list);

        return list;
    }

    // Reemplaza variables en las rutas
    auto List::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string {
        std::string result = path;

        // Reemplazar ${PREFIX}
        size_t pos = 0;
        while ((pos = result.find("${PREFIX}", pos)) != std::string::npos) {
            result.replace(pos, 9, prefix);  // 9 = longitud de "${PREFIX}"
            pos += prefix.length();
        }

        // Reemplazar ${SYSTEM_FOLDER}
        pos = 0;
        while ((pos = result.find("${SYSTEM_FOLDER}", pos)) != std::string::npos) {
            result.replace(pos, 16, system_folder);  // 16 = longitud de "${SYSTEM_FOLDER}"
            pos += system_folder.length();
        }

        return result;
    }

    // Parsea las opciones de una línea de configuración
    auto List::parseOptions(const std::string& options, bool& required, bool& absolute) -> void {
        if (options.empty()) {
            return;
        }

        std::istringstream iss(options);
        std::string option;

        while (std::getline(iss, option, ',')) {
            // Eliminar espacios
            option.erase(0, option.find_first_not_of(" \t"));
            option.erase(option.find_last_not_of(" \t") + 1);

            if (option == "optional") {
                required = false;
            } else if (option == "absolute") {
                absolute = true;
            }
        }
    }

}  // namespace Resource
