// SPDX-License-Identifier: GPL-3.0-or-later

#include "Database.h"

const char *ml::Database::SQL_CREATE_ANOMALIES_TABLE =
    "CREATE TABLE IF NOT EXISTS anomaly_events( "
    "     anomaly_detector_name text NOT NULL, "
    "     anomaly_detector_version int NOT NULL, "
    "     host_id text NOT NULL, "
    "     after int NOT NULL, "
    "     before int NOT NULL, "
    "     anomaly_event_info text, "
    "     PRIMARY KEY( "
    "         anomaly_detector_name, anomaly_detector_version, "
    "         host_id, after, before "
    "     ) "
    ");";

const char *ml::Database::SQL_INSERT_ANOMALY =
    "INSERT INTO anomaly_events( "
    "     anomaly_detector_name, anomaly_detector_version, "
    "     host_id, after, before, anomaly_event_info) "
    "VALUES (?1, ?2, ?3, ?4, ?5, ?6);";

const char *ml::Database::SQL_SELECT_ANOMALY =
    "SELECT anomaly_event_info FROM anomaly_events WHERE"
    "   anomaly_detector_name == ?1 AND"
    "   anomaly_detector_version == ?2 AND"
    "   host_id == ?3 AND"
    "   after == ?4 AND"
    "   before == ?5;";

const char *ml::Database::SQL_SELECT_ANOMALY_EVENTS =
    "SELECT after, before FROM anomaly_events WHERE"
    "   anomaly_detector_name == ?1 AND"
    "   anomaly_detector_version == ?2 AND"
    "   host_id == ?3 AND"
    "   after >= ?4 AND"
    "   before <= ?5;";

using namespace ml;

bool Statement::prepare(sqlite3 *Conn) {
    if (!Conn)
        return false;

    if (ParsedStmt)
        return true;

    int RC = sqlite3_prepare_v2(Conn, RawStmt, -1, &ParsedStmt, nullptr);
    if (RC == SQLITE_OK)
        return true;

    std::string Msg = "Statement \"%s\" preparation failed due to \"%s\"";
    error(Msg.c_str(), RawStmt, sqlite3_errstr(RC));

    return false;
}

bool Statement::bindValue(size_t Pos, const std::string &Value) {
    int RC = sqlite3_bind_text(ParsedStmt, Pos, Value.c_str(), -1, SQLITE_TRANSIENT);
    if (RC == SQLITE_OK)
        return true;

    error("Failed to bind text '%s' (pos = %zu) in statement '%s'.", Value.c_str(), Pos, RawStmt);
    return false;
}

bool Statement::bindValue(size_t Pos, const int Value) {
    int RC = sqlite3_bind_int(ParsedStmt, Pos, Value);
    if (RC == SQLITE_OK)
        return true;

    error("Failed to bind integer %d (pos = %zu) in statement '%s'.", Value, Pos, RawStmt);
    return false;
}

bool Statement::resetAndClear(bool Ret) {
    int RC = sqlite3_reset(ParsedStmt);
    if (RC != SQLITE_OK) {
        error("Could not reset statement: '%s'", RawStmt);
        return false;
    }

    RC = sqlite3_clear_bindings(ParsedStmt);
    if (RC != SQLITE_OK) {
        error("Could not clear bindings in statement: '%s'", RawStmt);
        return false;
    }

    return Ret;
}

Database::Database(const std::string &Path) {
    // Get sqlite3 connection handle.
    int RC = sqlite3_open(Path.c_str(), &Conn);
    if (RC != SQLITE_OK) {
        std::string Msg = "Failed to initialize ML DB at %s, due to \"%s\"";
        error(Msg.c_str(), Path.c_str(), sqlite3_errstr(RC));

        sqlite3_close(Conn);
        Conn = nullptr;
        return;
    }

    // Create anomaly events table if it does not exist.
    char *ErrMsg;
    RC = sqlite3_exec(Conn, SQL_CREATE_ANOMALIES_TABLE, nullptr, nullptr, &ErrMsg);
    if (RC == SQLITE_OK)
        return;

    error("SQLite error during database initialization, rc = %d (%s)", RC, ErrMsg);
    error("SQLite failed statement: %s", SQL_CREATE_ANOMALIES_TABLE);

    sqlite3_free(ErrMsg);
    sqlite3_close(Conn);
    Conn = nullptr;
}

Database::~Database() {
    if (!Conn)
        return;

    int RC = sqlite3_close(Conn);
    if (RC != SQLITE_OK)
        error("Could not close connection properly (rc=%d)", RC);
}
