#include "gfx/shaderlab_db.h"
#include <sqlite3.h>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>

namespace fn::gfx {

static sqlite3*    g_db = nullptr;
static std::string g_path;

static std::string now_iso() {
    auto t  = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    std::tm tm_utc{};
#if defined(_WIN32)
    gmtime_s(&tm_utc, &t);
#else
    gmtime_r(&t, &tm_utc);
#endif
    std::ostringstream ss;
    ss << std::put_time(&tm_utc, "%Y-%m-%dT%H:%M:%SZ");
    return ss.str();
}

// ── Serialization helpers (custom compact format, no JSON parser needed) ───
// floats:   "1.0,2.5,-0.3"
// strings:  one per line, '\n' separator (labels may contain spaces but not LF)
// controls: one per line, fields separated by '|':
//             kind(int)|label|p0|p1|p2|min|max|step

static std::string floats_to_csv(const std::vector<float>& v) {
    std::ostringstream ss;
    for (size_t i = 0; i < v.size(); ++i) {
        if (i) ss << ',';
        ss << v[i];
    }
    return ss.str();
}

static std::vector<float> floats_from_csv(const std::string& s) {
    std::vector<float> out;
    if (s.empty()) return out;
    std::istringstream ss(s);
    std::string tok;
    while (std::getline(ss, tok, ',')) {
        try { out.push_back(std::stof(tok)); } catch (...) {}
    }
    return out;
}

static std::string strings_to_lf(const std::vector<std::string>& v) {
    std::ostringstream ss;
    for (size_t i = 0; i < v.size(); ++i) {
        if (i) ss << '\n';
        ss << v[i];
    }
    return ss.str();
}

static std::vector<std::string> strings_from_lf(const std::string& s) {
    std::vector<std::string> out;
    if (s.empty()) return out;
    std::istringstream ss(s);
    std::string line;
    while (std::getline(ss, line)) out.push_back(line);
    return out;
}

static std::string controls_to_string(const std::vector<DagControl>& v) {
    std::ostringstream ss;
    for (size_t i = 0; i < v.size(); ++i) {
        if (i) ss << '\n';
        ss << static_cast<int>(v[i].kind) << '|'
           << v[i].label << '|'
           << v[i].param_idx[0] << '|'
           << v[i].param_idx[1] << '|'
           << v[i].param_idx[2] << '|'
           << v[i].min  << '|'
           << v[i].max  << '|'
           << v[i].step;
    }
    return ss.str();
}

static std::vector<DagControl> controls_from_string(const std::string& s) {
    std::vector<DagControl> out;
    if (s.empty()) return out;
    std::istringstream ss(s);
    std::string line;
    while (std::getline(ss, line)) {
        DagControl c;
        // Split by '|' into 8 fields
        std::vector<std::string> fields;
        std::string buf;
        for (char ch : line) {
            if (ch == '|') { fields.push_back(buf); buf.clear(); }
            else            buf.push_back(ch);
        }
        fields.push_back(buf);
        if (fields.size() < 8) continue;
        try {
            c.kind          = static_cast<DagControl::Kind>(std::stoi(fields[0]));
            c.label         = fields[1];
            c.param_idx[0]  = std::stoi(fields[2]);
            c.param_idx[1]  = std::stoi(fields[3]);
            c.param_idx[2]  = std::stoi(fields[4]);
            c.min           = std::stof(fields[5]);
            c.max           = std::stof(fields[6]);
            c.step          = std::stof(fields[7]);
            out.push_back(c);
        } catch (...) {}
    }
    return out;
}

// ── DB lifecycle ──────────────────────────────────────────────────────────

static bool exec(const char* sql, std::string* err) {
    char* msg = nullptr;
    int rc = sqlite3_exec(g_db, sql, nullptr, nullptr, &msg);
    if (rc != SQLITE_OK) {
        if (err) *err = msg ? msg : "sqlite_exec failed";
        if (msg) sqlite3_free(msg);
        return false;
    }
    return true;
}

bool shaderlab_db_open(const std::string& path) {
    if (g_db && path == g_path) return true;
    if (g_db) shaderlab_db_close();

    int rc = sqlite3_open(path.c_str(), &g_db);
    if (rc != SQLITE_OK) {
        if (g_db) { sqlite3_close(g_db); g_db = nullptr; }
        return false;
    }
    g_path = path;

    const char* schema =
        "CREATE TABLE IF NOT EXISTS generators ("
        "  id              TEXT PRIMARY KEY,"
        "  label           TEXT NOT NULL,"
        "  description     TEXT NOT NULL DEFAULT '',"
        "  source_glsl     TEXT NOT NULL,"
        "  body_glsl       TEXT NOT NULL,"
        "  param_count     INTEGER NOT NULL,"
        "  param_defaults  TEXT NOT NULL,"
        "  param_names     TEXT NOT NULL,"
        "  controls        TEXT NOT NULL,"
        "  tags            TEXT NOT NULL DEFAULT '',"
        "  created_at      TEXT NOT NULL,"
        "  updated_at      TEXT NOT NULL"
        ");";
    return exec(schema, nullptr);
}

void shaderlab_db_close() {
    if (g_db) sqlite3_close(g_db);
    g_db = nullptr;
    g_path.clear();
}

// ── CRUD ──────────────────────────────────────────────────────────────────

bool shaderlab_db_save_generator(GeneratorRecord& gen, std::string* err) {
    if (!g_db) { if (err) *err = "db not open"; return false; }

    const std::string ts = now_iso();
    if (gen.created_at.empty()) gen.created_at = ts;
    gen.updated_at = ts;

    const char* sql =
        "INSERT INTO generators "
        "(id,label,description,source_glsl,body_glsl,param_count,param_defaults,param_names,controls,tags,created_at,updated_at) "
        "VALUES (?,?,?,?,?,?,?,?,?,?,?,?) "
        "ON CONFLICT(id) DO UPDATE SET "
        " label=excluded.label, description=excluded.description, "
        " source_glsl=excluded.source_glsl, body_glsl=excluded.body_glsl, "
        " param_count=excluded.param_count, param_defaults=excluded.param_defaults, "
        " param_names=excluded.param_names, controls=excluded.controls, "
        " tags=excluded.tags, updated_at=excluded.updated_at;";

    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
        if (err) *err = sqlite3_errmsg(g_db);
        return false;
    }

    const std::string defaults_csv = floats_to_csv(gen.param_defaults);
    const std::string names_lf     = strings_to_lf(gen.param_names);
    const std::string controls_str = controls_to_string(gen.controls);

    sqlite3_bind_text(stmt, 1,  gen.id.c_str(),          -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2,  gen.label.c_str(),       -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3,  gen.description.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4,  gen.source_glsl.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 5,  gen.body_glsl.c_str(),   -1, SQLITE_TRANSIENT);
    sqlite3_bind_int (stmt, 6,  gen.param_count);
    sqlite3_bind_text(stmt, 7,  defaults_csv.c_str(),    -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 8,  names_lf.c_str(),        -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 9,  controls_str.c_str(),    -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 10, gen.tags.c_str(),        -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 11, gen.created_at.c_str(),  -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 12, gen.updated_at.c_str(),  -1, SQLITE_TRANSIENT);

    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) {
        if (err) *err = sqlite3_errmsg(g_db);
        return false;
    }
    return true;
}

static GeneratorRecord row_to_record(sqlite3_stmt* stmt) {
    auto col = [&](int i) -> std::string {
        const unsigned char* s = sqlite3_column_text(stmt, i);
        return s ? reinterpret_cast<const char*>(s) : "";
    };
    GeneratorRecord r;
    r.id              = col(0);
    r.label           = col(1);
    r.description     = col(2);
    r.source_glsl     = col(3);
    r.body_glsl       = col(4);
    r.param_count     = sqlite3_column_int(stmt, 5);
    r.param_defaults  = floats_from_csv(col(6));
    r.param_names     = strings_from_lf(col(7));
    r.controls        = controls_from_string(col(8));
    r.tags            = col(9);
    r.created_at      = col(10);
    r.updated_at      = col(11);
    return r;
}

std::vector<GeneratorRecord> shaderlab_db_list_generators() {
    std::vector<GeneratorRecord> out;
    if (!g_db) return out;
    const char* sql =
        "SELECT id,label,description,source_glsl,body_glsl,param_count,"
        "       param_defaults,param_names,controls,tags,created_at,updated_at "
        "FROM generators ORDER BY label;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) != SQLITE_OK) return out;
    while (sqlite3_step(stmt) == SQLITE_ROW) out.push_back(row_to_record(stmt));
    sqlite3_finalize(stmt);
    return out;
}

bool shaderlab_db_get_generator(const std::string& id, GeneratorRecord& out) {
    if (!g_db) return false;
    const char* sql =
        "SELECT id,label,description,source_glsl,body_glsl,param_count,"
        "       param_defaults,param_names,controls,tags,created_at,updated_at "
        "FROM generators WHERE id = ?;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) != SQLITE_OK) return false;
    sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
    bool found = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        out = row_to_record(stmt);
        found = true;
    }
    sqlite3_finalize(stmt);
    return found;
}

bool shaderlab_db_delete_generator(const std::string& id) {
    if (!g_db) return false;
    const char* sql = "DELETE FROM generators WHERE id = ?;";
    sqlite3_stmt* stmt = nullptr;
    if (sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr) != SQLITE_OK) return false;
    sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
    int rc = sqlite3_step(stmt);
    int changes = sqlite3_changes(g_db);
    sqlite3_finalize(stmt);
    return rc == SQLITE_DONE && changes > 0;
}

sqlite3* shaderlab_db_handle() {
    return g_db;
}

} // namespace fn::gfx

#ifdef SHADERLAB_DB_TEST
#include <cassert>
#include <cstdio>

int main() {
    using namespace fn::gfx;

    assert(shaderlab_db_open(":memory:"));

    // 1. List on empty db
    {
        auto v = shaderlab_db_list_generators();
        assert(v.empty());
    }

    // 2. Save + get
    {
        GeneratorRecord g;
        g.id           = "watercolor";
        g.label        = "watercolor";
        g.description  = "soft pastel blob";
        g.source_glsl  = "uniform float u_speed; void main() { fragColor = vec4(1.0); }";
        g.body_glsl    = "    return vec4(1.0);";
        g.param_count  = 1;
        g.param_defaults = {0.7f};
        g.param_names    = {"speed"};
        g.controls = {
            { DagControl::Kind::Slider, "velocidad", {0, -1, -1}, 0.0f, 3.0f, 0.01f },
        };
        g.tags = "shaders_lab,user";

        std::string err;
        assert(shaderlab_db_save_generator(g, &err));
        assert(!g.created_at.empty());
        assert(!g.updated_at.empty());

        GeneratorRecord r;
        assert(shaderlab_db_get_generator("watercolor", r));
        assert(r.label == "watercolor");
        assert(r.param_count == 1);
        assert(r.param_defaults.size() == 1 && r.param_defaults[0] == 0.7f);
        assert(r.param_names.size() == 1 && r.param_names[0] == "speed");
        assert(r.controls.size() == 1);
        assert(r.controls[0].kind == DagControl::Kind::Slider);
        assert(r.controls[0].label == "velocidad");
        assert(r.controls[0].param_idx[0] == 0);
        assert(r.controls[0].max == 3.0f);
    }

    // 3. List returns the saved record
    {
        auto v = shaderlab_db_list_generators();
        assert(v.size() == 1);
        assert(v[0].id == "watercolor");
    }

    // 4. Save second generator with multiple controls
    {
        GeneratorRecord g;
        g.id          = "chrome";
        g.label       = "chrome";
        g.source_glsl = "// stub";
        g.body_glsl   = "    return vec4(0.0);";
        g.param_count = 4;
        g.param_defaults = {1.0f, 2.0f, 0.5f, 0.5f};
        g.param_names    = {"a", "b", "c", "d"};
        g.controls = {
            { DagControl::Kind::XY,    "centro", {0, 1, -1}, -1.0f, 1.0f, 0.01f },
            { DagControl::Kind::Color, "tinte",  {1, 2, 3}, 0.0f, 1.0f, 0.0f },
        };
        assert(shaderlab_db_save_generator(g));
    }

    // 5. List ordered by label: chrome then watercolor
    {
        auto v = shaderlab_db_list_generators();
        assert(v.size() == 2);
        assert(v[0].id == "chrome");
        assert(v[1].id == "watercolor");
        assert(v[0].controls.size() == 2);
        assert(v[0].controls[1].kind == DagControl::Kind::Color);
        assert(v[0].controls[1].param_idx[2] == 3);
    }

    // 6. Update preserves created_at, bumps updated_at
    {
        GeneratorRecord g;
        assert(shaderlab_db_get_generator("watercolor", g));
        std::string created = g.created_at;
        g.label = "watercolor v2";
        // Force a different timestamp by setting created_at; save will set updated_at = now
        assert(shaderlab_db_save_generator(g));
        GeneratorRecord r;
        assert(shaderlab_db_get_generator("watercolor", r));
        assert(r.label == "watercolor v2");
        assert(r.created_at == created);
    }

    // 7. Delete
    {
        assert(shaderlab_db_delete_generator("watercolor"));
        GeneratorRecord r;
        assert(!shaderlab_db_get_generator("watercolor", r));
        auto v = shaderlab_db_list_generators();
        assert(v.size() == 1);
        assert(!shaderlab_db_delete_generator("nonexistent"));
    }

    shaderlab_db_close();
    std::printf("shaderlab_db: 7/7 asserts passed\n");
    return 0;
}
#endif
