#include "game/gameplay/manage_hiscore_table.hpp"

#include <SDL3/SDL.h>  // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo

#include <algorithm>   // Para __sort_fn, sort
#include <array>       // Para array
#include <functional>  // Para identity
#include <iomanip>     // Para std::setw, std::setfill
#include <iostream>    // Para std::cout
#include <iterator>    // Para distance
#include <ranges>      // Para __find_if_fn, find_if
#include <utility>     // Para move

#include "utils/utils.hpp"  // Para getFileName

// Resetea la tabla a los valores por defecto
void ManageHiScoreTable::clear() {
    // Limpia la tabla
    table_.clear();

    // Añade 10 entradas predefinidas
    table_.emplace_back("BRY", 1000000);
    table_.emplace_back("USUFO", 500000);
    table_.emplace_back("GLUCA", 100000);
    table_.emplace_back("PARRA", 50000);
    table_.emplace_back("CAGAM", 10000);
    table_.emplace_back("PEPE", 5000);
    table_.emplace_back("ROSIT", 1000);
    table_.emplace_back("SAM", 500);
    table_.emplace_back("PACMQ", 200);
    table_.emplace_back("PELEC", 100);

    /*
    table_.emplace_back("BRY", 1000);
    table_.emplace_back("USUFO", 500);
    table_.emplace_back("GLUCA", 100);
    table_.emplace_back("PARRA", 50);
    table_.emplace_back("CAGAM", 10);
    table_.emplace_back("PEPE", 5);
    table_.emplace_back("ROSIT", 4);
    table_.emplace_back("SAM", 3);
    table_.emplace_back("PACMQ", 2);
    table_.emplace_back("PELEC", 1);
    */

    /*
    table_.emplace_back("BRY", 5000000);
    table_.emplace_back("USUFO", 5000000);
    table_.emplace_back("GLUCA", 5000000);
    table_.emplace_back("PARRA", 5000000);
    table_.emplace_back("CAGAM", 5000000);
    table_.emplace_back("PEPE", 5000000);
    table_.emplace_back("ROSIT", 5000000);
    table_.emplace_back("SAM", 5000000);
    table_.emplace_back("PACMQ", 5000000);
    table_.emplace_back("PELEC", 5000000);
    */

    sort();
}

// Añade un elemento a la tabla
auto ManageHiScoreTable::add(const HiScoreEntry& entry) -> int {
    // Añade la entrada a la tabla
    table_.push_back(entry);

    // Ordena la tabla
    sort();

    // Encontrar la posición del nuevo elemento
    auto it = std::ranges::find_if(table_, [&](const HiScoreEntry& e) -> bool {
        return e.name == entry.name && e.score == entry.score && e.one_credit_complete == entry.one_credit_complete;
    });

    int position = -1;
    if (it != table_.end()) {
        position = std::distance(table_.begin(), it);
    }

    // Deja solo las 10 primeras entradas
    if (table_.size() > 10) {
        table_.resize(10);

        // Si el nuevo elemento quedó fuera del top 10
        if (position >= 10) {
            position = NO_ENTRY;  // No entró en el top 10
        }
    }

    // Devuelve la posición
    return position;
}

// Ordena la tabla
void ManageHiScoreTable::sort() {
    struct
    {
        auto operator()(const HiScoreEntry& a, const HiScoreEntry& b) const -> bool { return a.score > b.score; }
    } score_descending_comparator;

    std::ranges::sort(table_, score_descending_comparator);
}

// Carga la tabla desde un fichero
auto ManageHiScoreTable::loadFromFile(const std::string& file_path) -> bool {
    auto* file = SDL_IOFromFile(file_path.c_str(), "rb");

    if (file == nullptr) {
        std::cout << "Error: Unable to load " << getFileName(file_path) << " file! " << SDL_GetError() << '\n';
        clear();
        return false;
    }

    // Validar header (magic number + version + table size)
    if (!validateMagicNumber(file, file_path)) {
        SDL_CloseIO(file);
        clear();
        return false;
    }

    if (!validateVersion(file, file_path)) {
        SDL_CloseIO(file);
        clear();
        return false;
    }

    int table_size = 0;
    if (!readTableSize(file, file_path, table_size)) {
        SDL_CloseIO(file);
        clear();
        return false;
    }

    // Leer todas las entradas
    Table temp_table;
    bool success = true;
    for (int i = 0; i < table_size; ++i) {
        HiScoreEntry entry;
        if (!readEntry(file, file_path, i, entry)) {
            success = false;
            break;
        }
        temp_table.push_back(entry);
    }

    // Verificar checksum
    if (success) {
        success = verifyChecksum(file, file_path, temp_table);
    }

    SDL_CloseIO(file);

    // Si todo fue bien, actualizar la tabla; si no, usar valores por defecto
    if (success) {
        table_ = std::move(temp_table);
    } else {
        std::cout << "File " << getFileName(file_path) << " is corrupted - loading default values" << '\n';
        clear();
    }

    return success;
}

// Métodos auxiliares privados para loadFromFile

auto ManageHiScoreTable::validateMagicNumber(SDL_IOStream* file, const std::string& file_path) -> bool {
    std::array<char, 4> magic;
    if (SDL_ReadIO(file, magic.data(), 4) != 4 || magic[0] != 'C' || magic[1] != 'C' || magic[2] != 'A' || magic[3] != 'E') {
        std::cout << "Error: Invalid magic number in " << getFileName(file_path) << " - file may be corrupted or old format" << '\n';
        return false;
    }
    return true;
}

auto ManageHiScoreTable::validateVersion(SDL_IOStream* file, const std::string& file_path) -> bool {
    int version = 0;
    if (SDL_ReadIO(file, &version, sizeof(int)) != sizeof(int)) {
        std::cout << "Error: Cannot read version in " << getFileName(file_path) << '\n';
        return false;
    }

    if (version != FILE_VERSION) {
        std::cout << "Error: Unsupported file version " << version << " in " << getFileName(file_path) << " (expected " << FILE_VERSION << ")" << '\n';
        return false;
    }
    return true;
}

auto ManageHiScoreTable::readTableSize(SDL_IOStream* file, const std::string& file_path, int& table_size) -> bool {
    if (SDL_ReadIO(file, &table_size, sizeof(int)) != sizeof(int)) {
        std::cout << "Error: Cannot read table size in " << getFileName(file_path) << '\n';
        return false;
    }

    if (table_size < 0 || table_size > MAX_TABLE_SIZE) {
        std::cout << "Error: Invalid table size " << table_size << " in " << getFileName(file_path) << " (expected 0-" << MAX_TABLE_SIZE << ")" << '\n';
        return false;
    }
    return true;
}

auto ManageHiScoreTable::readEntry(SDL_IOStream* file, const std::string& file_path, int index, HiScoreEntry& entry) -> bool {
    // Leer y validar puntuación
    if (SDL_ReadIO(file, &entry.score, sizeof(int)) != sizeof(int)) {
        std::cout << "Error: Cannot read score for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }

    if (entry.score < 0 || entry.score > MAX_SCORE) {
        std::cout << "Error: Invalid score " << entry.score << " for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }

    // Leer y validar tamaño del nombre
    int name_size = 0;
    if (SDL_ReadIO(file, &name_size, sizeof(int)) != sizeof(int)) {
        std::cout << "Error: Cannot read name size for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }

    if (name_size < 0 || name_size > MAX_NAME_SIZE) {
        std::cout << "Error: Invalid name size " << name_size << " for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }

    // Leer el nombre
    std::vector<char> name_buffer(name_size + 1);
    if (SDL_ReadIO(file, name_buffer.data(), name_size) != static_cast<size_t>(name_size)) {
        std::cout << "Error: Cannot read name for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }
    name_buffer[name_size] = '\0';
    entry.name = std::string(name_buffer.data());

    // Leer one_credit_complete
    int occ_value = 0;
    if (SDL_ReadIO(file, &occ_value, sizeof(int)) != sizeof(int)) {
        std::cout << "Error: Cannot read one_credit_complete for entry " << index << " in " << getFileName(file_path) << '\n';
        return false;
    }
    entry.one_credit_complete = (occ_value != 0);

    return true;
}

auto ManageHiScoreTable::verifyChecksum(SDL_IOStream* file, const std::string& file_path, const Table& temp_table) -> bool {
    unsigned int stored_checksum = 0;
    if (SDL_ReadIO(file, &stored_checksum, sizeof(unsigned int)) != sizeof(unsigned int)) {
        std::cout << "Error: Cannot read checksum in " << getFileName(file_path) << '\n';
        return false;
    }

    unsigned int calculated_checksum = calculateChecksum(temp_table);
    if (stored_checksum != calculated_checksum) {
        std::cout << "Error: Checksum mismatch in " << getFileName(file_path) << " (stored: 0x" << std::hex << std::setw(8) << std::setfill('0') << stored_checksum << ", calculated: 0x" << std::setw(8) << std::setfill('0') << calculated_checksum << std::dec << ") - file is corrupted" << '\n';
        return false;
    }
    return true;
}

// Calcula checksum de la tabla
auto ManageHiScoreTable::calculateChecksum(const Table& table) -> unsigned int {
    unsigned int checksum = 0x12345678;  // Magic seed

    for (const auto& entry : table) {
        // Checksum del score
        checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(entry.score);

        // Checksum del nombre
        for (char c : entry.name) {
            checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(c);
        }

        // Checksum de one_credit_complete
        checksum = ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
    }

    return checksum;
}

// Guarda la tabla en un fichero
auto ManageHiScoreTable::saveToFile(const std::string& file_path) -> bool {
    auto success = true;
    auto* file = SDL_IOFromFile(file_path.c_str(), "w+b");

    if (file != nullptr) {
        // Escribe magic number "CCAE"
        constexpr std::array<char, 4> MAGIC = {'C', 'C', 'A', 'E'};
        SDL_WriteIO(file, MAGIC.data(), 4);

        // Escribe versión del formato
        int version = FILE_VERSION;
        SDL_WriteIO(file, &version, sizeof(int));

        // Guarda el número de entradas en la tabla
        int table_size = static_cast<int>(table_.size());
        SDL_WriteIO(file, &table_size, sizeof(int));

        // Guarda los datos de cada entrada
        for (int i = 0; i < table_size; ++i) {
            const HiScoreEntry& entry = table_.at(i);

            // Guarda la puntuación
            SDL_WriteIO(file, &entry.score, sizeof(int));

            // Guarda el tamaño del nombre y luego el nombre
            int name_size = static_cast<int>(entry.name.size());
            SDL_WriteIO(file, &name_size, sizeof(int));
            SDL_WriteIO(file, entry.name.c_str(), name_size);

            // Guarda el valor de one_credit_complete como un entero (0 o 1)
            int occ_value = entry.one_credit_complete ? 1 : 0;
            SDL_WriteIO(file, &occ_value, sizeof(int));
        }

        // Calcula y escribe el checksum
        unsigned int checksum = calculateChecksum(table_);
        SDL_WriteIO(file, &checksum, sizeof(unsigned int));

        SDL_CloseIO(file);
    } else {
        std::cout << "Error: Unable to save " << getFileName(file_path) << " file! " << SDL_GetError() << '\n';
        success = false;
    }
    return success;
}