#include "layout_store.h"

#include "viz/graph_types.h"

#include <sqlite3.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <string>

namespace ge {

namespace {
sqlite3* g_db = nullptr;
} // namespace

// FNV1a 64 sobre el path. No requiere normalizacion canonica (el caller pasa
// el path tal cual lo recibio en argv) — basta con que el mismo path produzca
// el mismo hash entre runs.
uint64_t compute_graph_hash(const char* path) {
    if (!path) return 0;
    uint64_t h = 1469598103934665603ull; // FNV offset
    while (*path) {
        h ^= (uint64_t)(unsigned char)(*path++);
        h *= 1099511628211ull; // FNV prime
    }
    return h;
}

bool layout_store_open(const char* db_path) {
    if (g_db) return true;
    if (!db_path) return false;
    int rc = sqlite3_open(db_path, &g_db);
    if (rc != SQLITE_OK) {
        std::fprintf(stderr, "[layout_store] sqlite3_open failed: %s\n",
                     sqlite3_errmsg(g_db));
        if (g_db) sqlite3_close(g_db);
        g_db = nullptr;
        return false;
    }
    const char* schema =
        "CREATE TABLE IF NOT EXISTS layouts ("
        "  graph_hash TEXT NOT NULL,"
        "  node_id    TEXT NOT NULL,"
        "  x          REAL NOT NULL,"
        "  y          REAL NOT NULL,"
        "  pinned     INTEGER NOT NULL DEFAULT 0,"
        "  updated_at INTEGER NOT NULL,"
        "  PRIMARY KEY(graph_hash, node_id)"
        ");";
    char* err = nullptr;
    rc = sqlite3_exec(g_db, schema, nullptr, nullptr, &err);
    if (rc != SQLITE_OK) {
        std::fprintf(stderr, "[layout_store] schema error: %s\n",
                     err ? err : "(null)");
        sqlite3_free(err);
        sqlite3_close(g_db);
        g_db = nullptr;
        return false;
    }
    return true;
}

void layout_store_close() {
    if (g_db) {
        sqlite3_close(g_db);
        g_db = nullptr;
    }
}

namespace {
std::string hash_to_hex(uint64_t h) {
    char buf[17];
    std::snprintf(buf, sizeof(buf), "%016llx",
                  (unsigned long long)h);
    return std::string(buf);
}

std::string node_user_to_hex(uint64_t u) {
    char buf[17];
    std::snprintf(buf, sizeof(buf), "%016llx",
                  (unsigned long long)u);
    return std::string(buf);
}
} // namespace

int layout_store_save(uint64_t graph_hash, const GraphData& graph) {
    if (!g_db || graph.node_count <= 0) return 0;

    sqlite3_exec(g_db, "BEGIN", nullptr, nullptr, nullptr);

    const char* sql =
        "INSERT INTO layouts(graph_hash, node_id, x, y, pinned, updated_at) "
        "VALUES(?, ?, ?, ?, ?, ?) "
        "ON CONFLICT(graph_hash, node_id) DO UPDATE SET "
        "  x=excluded.x, y=excluded.y, pinned=excluded.pinned, "
        "  updated_at=excluded.updated_at;";
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr);
    if (rc != SQLITE_OK) {
        std::fprintf(stderr, "[layout_store] prepare save failed: %s\n",
                     sqlite3_errmsg(g_db));
        sqlite3_exec(g_db, "ROLLBACK", nullptr, nullptr, nullptr);
        return -1;
    }

    std::string ghex = hash_to_hex(graph_hash);
    int written = 0;
    int64_t now = (int64_t)std::time(nullptr);

    for (int i = 0; i < graph.node_count; ++i) {
        const GraphNode& n = graph.nodes[i];
        if (n.user_data == 0) continue; // sin id estable: no persistible
        std::string nhex = node_user_to_hex(n.user_data);

        sqlite3_bind_text(stmt, 1, ghex.c_str(), -1, SQLITE_TRANSIENT);
        sqlite3_bind_text(stmt, 2, nhex.c_str(), -1, SQLITE_TRANSIENT);
        sqlite3_bind_double(stmt, 3, (double)n.x);
        sqlite3_bind_double(stmt, 4, (double)n.y);
        sqlite3_bind_int(stmt, 5, (n.flags & NF_PINNED) ? 1 : 0);
        sqlite3_bind_int64(stmt, 6, now);

        rc = sqlite3_step(stmt);
        if (rc == SQLITE_DONE) ++written;
        sqlite3_reset(stmt);
        sqlite3_clear_bindings(stmt);
    }

    sqlite3_finalize(stmt);
    sqlite3_exec(g_db, "COMMIT", nullptr, nullptr, nullptr);
    return written;
}

int layout_store_load(uint64_t graph_hash, GraphData& graph) {
    if (!g_db || graph.node_count <= 0) return 0;

    const char* sql =
        "SELECT node_id, x, y, pinned FROM layouts WHERE graph_hash = ?;";
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, nullptr);
    if (rc != SQLITE_OK) {
        std::fprintf(stderr, "[layout_store] prepare load failed: %s\n",
                     sqlite3_errmsg(g_db));
        return -1;
    }

    std::string ghex = hash_to_hex(graph_hash);
    sqlite3_bind_text(stmt, 1, ghex.c_str(), -1, SQLITE_TRANSIENT);

    int touched = 0;
    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
        const unsigned char* nid = sqlite3_column_text(stmt, 0);
        if (!nid) continue;
        uint64_t u = std::strtoull((const char*)nid, nullptr, 16);
        int idx = graph.find_node_by_user_data(u);
        if (idx < 0) continue;
        GraphNode& n = graph.nodes[idx];
        n.x = (float)sqlite3_column_double(stmt, 1);
        n.y = (float)sqlite3_column_double(stmt, 2);
        if (sqlite3_column_int(stmt, 3))
            n.flags |= NF_PINNED;
        else
            n.flags &= ~NF_PINNED;
        ++touched;
    }
    sqlite3_finalize(stmt);
    return touched;
}

} // namespace ge
