#include "database.hpp"
#include "asset_io.hpp"
#include <SQLiteCpp/Database.h>
#include <SDL3/SDL.h>
#include <iostream>
#include <vector>

std::string get_writable_db_path() {
    char *pref = SDL_GetPrefPath("Critterfolio", "CritterFolio");
    std::string path = pref ? pref : "./";
    if (pref) {
        SDL_free(pref);
    }
    path += "local.db";
    return path;
}

bool ensure_db_copied() {
    std::string dest_path = get_writable_db_path();

    SDL_IOStream *check = SDL_IOFromFile(dest_path.c_str(), "rb");
    if (check) {
        SDL_CloseIO(check);
        return true;
    }

    SDL_IOStream *src = OpenAssetFile("./assets/local.db");
    if (!src) {
        std::cout << "[ERROR] Could not open seed database in assets: " << SDL_GetError() << "\n";
        return false;
    }

    Sint64 size = SDL_GetIOSize(src);
    if (size < 0) {
        std::cout << "[ERROR] Could not determine seed database size.\n";
        SDL_CloseIO(src);
        return false;
    }

    std::vector<Uint8> buffer(static_cast<size_t>(size));
    if (SDL_ReadIO(src, buffer.data(), buffer.size()) != buffer.size()) {
        std::cout << "[ERROR] Failed reading seed database.\n";
        SDL_CloseIO(src);
        return false;
    }
    SDL_CloseIO(src);

    SDL_IOStream *dst = SDL_IOFromFile(dest_path.c_str(), "wb");
    if (!dst) {
        std::cout << "[ERROR] Could not open destination database for writing: " << SDL_GetError() << "\n";
        return false;
    }

    bool ok = SDL_WriteIO(dst, buffer.data(), buffer.size()) == buffer.size();
    SDL_CloseIO(dst);

    if (!ok) {
        std::cout << "[ERROR] Failed writing copied database.\n";
    }
    return ok;
}

bool db_get_all_critters(std::map<int, Critter> &critters) {
    try {
        SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
        SQLite::Statement q(db, "SELECT * FROM critters");

        critters.clear();

        while (q.executeStep()) {
            int id = q.getColumn(0);

            critters.try_emplace(
                id,
                id,
                q.getColumn(1).getText(),
                q.getColumn(2).getText(),
                q.getColumn(3).getInt(),
                q.getColumn(4).getInt(),
                q.getColumn(5).getText(),
                q.getColumn(6).getText()
            );
        }
    }
    catch (const std::exception &e) {
        std::cout << "[ERROR] DB exception: " << e.what() << std::endl;
        return false;
    }

    return true;
}

bool db_add_critter(const Critter &c, int &out_id) {
    try {
        SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE);
        SQLite::Statement q(db, "INSERT INTO critters (name, gender, mother_id, father_id, notes, species) VALUES (?, ?, ?, ?, ?, ?)");

        q.bind(1, c.name);
        q.bind(2, c.gender);
        q.bind(3, c.mother_id);
        q.bind(4, c.father_id);
        q.bind(5, c.notes);
        q.bind(6, c.species);

        q.exec();
        out_id = static_cast<int>(db.getLastInsertRowid());
        return true;
    }
    catch (const std::exception &e) {
        std::cout << "[ERROR] DB add exception: " << e.what() << std::endl;
        return false;
    }
}

bool db_delete_critter(int id) {
    try {
        SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE);
        SQLite::Statement q(db, "DELETE FROM critters WHERE id = ?");
        q.bind(1, id);
        q.exec();
        return true;
    }
    catch (const std::exception &e) {
        std::cout << "[ERROR] DB delete exception: " << e.what() << std::endl;
        return false;
    }
}

void db_print_all_critters(const std::map<int, Critter> &critters) {
    std::cout << "Current loaded critters:";
    for (const auto &[key, c] : critters) {
        std::cout << "\nCritter " << key << "\n";
        std::cout << "ID: " << c.id << std::endl;
        std::cout << "Name: " << c.name << std::endl;
        std::cout << "Gender: " << c.gender << std::endl;
        std::cout << "Mother ID: " << c.mother_id << std::endl;
        std::cout << "Father ID: " << c.father_id << std::endl;
        std::cout << "Notes: " << c.notes << std::endl;
        std::cout << "Species: " << c.species << std::endl;
    }
    std::cout << "\n";
}

bool validate_critter(const Critter &c, const std::map<int, Critter> &all_critters, std::string &out_error) {
    if (c.mother_id != 0 && c.father_id != 0 && c.mother_id == c.father_id) {
        out_error = "Mother ID and Father ID cannot be the same.";
        return false;
    }

    if (c.mother_id != 0) {
        auto it = all_critters.find(c.mother_id);
        if (it == all_critters.end()) {
            out_error = "Mother ID " + std::to_string(c.mother_id) + " does not exist.";
            return false;
        }

        const Critter &mother = it->second;

        if (mother.gender != "F" && mother.gender != "Female") {
            out_error = "Mother must have female gender ('F' or 'Female').";
            return false;
        }

        if (mother.species != c.species) {
            out_error = "Mother species ('" + mother.species + "') must match critter species ('" + c.species + "').";
            return false;
        }
    }

    if (c.father_id != 0) {
        auto it = all_critters.find(c.father_id);
        if (it == all_critters.end()) {
            out_error = "Father ID " + std::to_string(c.father_id) + " does not exist.";
            return false;
        }

        const Critter &father = it->second;

        if (father.gender != "M" && father.gender != "Male") {
            out_error = "Father must have male gender ('M' or 'Male').";
            return false;
        }

        if (father.species != c.species) {
            out_error = "Father species ('" + father.species + "') must match critter species ('" + c.species + "').";
            return false;
        }
    }

    return true;
}