/**
 * @file HBBlockingDB.cpp
 * @brief HB ACK rate-limiting database implementation.
 */

#include "HBBlockingDB.h"
#include "vendor/sqlite3/sqlite3.h"
#include "DriftingDateTime.h"

#include <QTimeZone>

namespace {
constexpr char SCHEMA[] =
    "CREATE TABLE IF NOT EXISTS hb_seen_v1 ("
    "  callsign VARCHAR(255) PRIMARY KEY, "
    "  last_seen TEXT"
    ");";
} // namespace

HBBlockingDB::HBBlockingDB(const QString &path)
    : path_{path}, db_{nullptr} {}

HBBlockingDB::~HBBlockingDB() { close(); }

bool HBBlockingDB::open() {
    int rc = sqlite3_open(path_.toLocal8Bit().data(), &db_);
    if (rc != SQLITE_OK) {
        close();
        return false;
    }

    rc = sqlite3_exec(db_, SCHEMA, nullptr, nullptr, nullptr);
    if (rc != SQLITE_OK) {
        close();
        return false;
    }

    return true;
}

void HBBlockingDB::close() {
    if (db_) {
        sqlite3_close(db_);
        db_ = nullptr;
    }
}

bool HBBlockingDB::isOpen() const { return db_ != nullptr; }

QString HBBlockingDB::error() const {
    if (db_) {
        return QString::fromLocal8Bit(sqlite3_errmsg(db_));
    }
    return {};
}

bool HBBlockingDB::upsertTimestamp(const QString &callsign,
                                   const QDateTime &ts) {
    if (!isOpen()) return false;

    const char *sql =
        "INSERT INTO hb_seen_v1 (callsign, last_seen) VALUES (?, ?) "
        "ON CONFLICT(callsign) DO UPDATE SET last_seen = excluded.last_seen;";

    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return false;

    auto c8 = callsign.toLocal8Bit();
    auto t8 = ts.toUTC().toString("yyyy-MM-dd HH:mm:ss").toLocal8Bit();
    sqlite3_bind_text(stmt, 1, c8.data(), -1, nullptr);
    sqlite3_bind_text(stmt, 2, t8.data(), -1, nullptr);
    sqlite3_step(stmt);

    return sqlite3_finalize(stmt) == SQLITE_OK;
}

QDateTime HBBlockingDB::getTimestamp(const QString &callsign) {
    if (!isOpen()) return {};

    const char *sql =
        "SELECT last_seen FROM hb_seen_v1 WHERE callsign = ? LIMIT 1;";

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

    auto c8 = callsign.toLocal8Bit();
    sqlite3_bind_text(stmt, 1, c8.data(), -1, nullptr);

    QDateTime result;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        auto raw = QByteArray(
            (const char *)sqlite3_column_text(stmt, 0),
            sqlite3_column_bytes(stmt, 0));
        result = QDateTime::fromString(
            QString::fromUtf8(raw), "yyyy-MM-dd HH:mm:ss");
        result.setTimeZone(QTimeZone::utc());
    }

    sqlite3_finalize(stmt);
    return result;
}

bool HBBlockingDB::deleteTimestamp(const QString &callsign) {
    if (!isOpen()) return false;

    const char *sql = "DELETE FROM hb_seen_v1 WHERE callsign = ?;";

    sqlite3_stmt *stmt;
    if (sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr) != SQLITE_OK)
        return false;

    auto c8 = callsign.toLocal8Bit();
    sqlite3_bind_text(stmt, 1, c8.data(), -1, nullptr);
    sqlite3_step(stmt);

    return sqlite3_finalize(stmt) == SQLITE_OK;
}
