#include "db.h"

#ifdef VOICECAT_HAS_NET

#include <chrono>
#include <cstring>
#include <random>
#include <stdexcept>

#include <sodium.h>
#include <sqlite3.h>

namespace voicecat::server {

// ── Schema ────────────────────────────────────────────────────────────────────

static constexpr int kCurrentSchemaVersion = 2;

static constexpr const char* kCreateSchemaV1 = R"sql(
CREATE TABLE IF NOT EXISTS accounts (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    username   TEXT    UNIQUE NOT NULL,
    pw_hash    TEXT    NOT NULL,
    is_admin   INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL,
    last_login INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS server_meta (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);
)sql";

static constexpr const char* kCreateSchemaV2 = R"sql(
CREATE TABLE IF NOT EXISTS channels (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    parent_id           INTEGER NOT NULL DEFAULT 0,
    name                TEXT    UNIQUE NOT NULL,
    topic               TEXT    NOT NULL DEFAULT '',
    password_hash       TEXT    NOT NULL DEFAULT '',
    max_users           INTEGER NOT NULL DEFAULT 0,
    type                INTEGER NOT NULL DEFAULT 0,
    audio_codec         INTEGER NOT NULL DEFAULT 0,
    audio_mode          INTEGER NOT NULL DEFAULT 0,
    audio_sample_rate   INTEGER NOT NULL DEFAULT 48000,
    audio_bitrate_bps   INTEGER NOT NULL DEFAULT 24000,
    audio_frame_ms      INTEGER NOT NULL DEFAULT 20,
    audio_application   INTEGER NOT NULL DEFAULT 0,
    audio_fec           INTEGER NOT NULL DEFAULT 1,
    audio_expected_packet_loss INTEGER NOT NULL DEFAULT 10,
    audio_dtx           INTEGER NOT NULL DEFAULT 1,
    audio_complexity    INTEGER NOT NULL DEFAULT 5,
    sort_order          INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS bans (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    subject_type  TEXT    NOT NULL,
    subject       TEXT    NOT NULL,
    reason        TEXT    NOT NULL DEFAULT '',
    expires_at    INTEGER NOT NULL DEFAULT 0,
    created_at    INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_bans_subject ON bans(subject_type, subject);
)sql";

// ── Database ──────────────────────────────────────────────────────────────────

Database::Database(std::string path) : path_(std::move(path)) {}

Database::~Database() {
    if (db_) { sqlite3_close(db_); db_ = nullptr; }
}

bool Database::open(std::string& error) {
    int rc = sqlite3_open(path_.c_str(), &db_);
    if (rc != SQLITE_OK) {
        error = sqlite3_errmsg(db_);
        sqlite3_close(db_);
        db_ = nullptr;
        return false;
    }
    sqlite3_busy_timeout(db_, 5000);
    // WAL mode for concurrency
    exec("PRAGMA journal_mode=WAL", error);
    exec("PRAGMA synchronous=NORMAL", error);
    error.clear();

    if (!exec(kCreateSchemaV1, error)) return false;
    if (!migrate(error)) return false;
    return true;
}

bool Database::migrate(std::string& error) {
    // Ensure server_meta row exists.
    if (!exec("INSERT OR IGNORE INTO server_meta (key, value) VALUES ('schema_version', '1')",
              error))
        return false;

    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "SELECT value FROM server_meta WHERE key='schema_version'",
                       -1, &stmt, nullptr);
    int version = 1;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        version = std::max(1, std::atoi(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0))));
    }
    sqlite3_finalize(stmt);

    if (version < 2) {
        if (!exec(kCreateSchemaV2, error)) return false;
        sqlite3_stmt* upd = nullptr;
        sqlite3_prepare_v2(db_,
            "INSERT OR REPLACE INTO server_meta (key, value) VALUES ('schema_version', ?)",
            -1, &upd, nullptr);
        sqlite3_bind_text(upd, 1, "2", -1, SQLITE_TRANSIENT);
        sqlite3_step(upd);
        sqlite3_finalize(upd);
    }
    return true;
}

bool Database::is_empty() {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "SELECT COUNT(*) FROM accounts", -1, &stmt, nullptr);
    int count = 0;
    if (sqlite3_step(stmt) == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
    sqlite3_finalize(stmt);
    return count == 0;
}

// ── Accounts ──────────────────────────────────────────────────────────────────

std::optional<Account> Database::create_account(const std::string& username,
                                                  const std::string& password,
                                                  bool is_admin, std::string& error) {
    if (username.empty() || password.empty()) {
        error = "username and password must not be empty";
        return std::nullopt;
    }
    // Hash with Argon2id via libsodium
    char hash[crypto_pwhash_STRBYTES];
    if (crypto_pwhash_str(hash, password.c_str(), password.size(),
                           crypto_pwhash_OPSLIMIT_INTERACTIVE,
                           crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
        error = "Argon2id hashing failed (OOM?)";
        return std::nullopt;
    }

    int64_t now = now_unix();
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db_,
        "INSERT INTO accounts (username, pw_hash, is_admin, created_at) VALUES (?,?,?,?)",
        -1, &stmt, nullptr);
    if (rc != SQLITE_OK) { error = sqlite3_errmsg(db_); return std::nullopt; }

    sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, hash, -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(stmt, 3, is_admin ? 1 : 0);
    sqlite3_bind_int64(stmt, 4, now);
    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (rc != SQLITE_DONE) {
        error = sqlite3_errmsg(db_);
        return std::nullopt;
    }

    Account acc;
    acc.id         = sqlite3_last_insert_rowid(db_);
    acc.username   = username;
    acc.is_admin   = is_admin;
    acc.created_at = now;
    return acc;
}

bool Database::reset_password(const std::string& username, const std::string& new_password,
                               std::string& error) {
    if (new_password.empty()) { error = "password must not be empty"; return false; }
    char hash[crypto_pwhash_STRBYTES];
    if (crypto_pwhash_str(hash, new_password.c_str(), new_password.size(),
                           crypto_pwhash_OPSLIMIT_INTERACTIVE,
                           crypto_pwhash_MEMLIMIT_INTERACTIVE) != 0) {
        error = "Argon2id hashing failed";
        return false;
    }
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "UPDATE accounts SET pw_hash=? WHERE username=?", -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, hash, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, username.c_str(), -1, SQLITE_TRANSIENT);
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return false; }
    if (sqlite3_changes(db_) == 0) { error = "user not found: " + username; return false; }
    return true;
}

bool Database::delete_account(const std::string& username, std::string& error) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "DELETE FROM accounts WHERE username=?", -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return false; }
    if (sqlite3_changes(db_) == 0) { error = "user not found: " + username; return false; }
    return true;
}

std::vector<Account> Database::list_accounts() {
    std::vector<Account> result;
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_,
        "SELECT id, username, is_admin, created_at, last_login FROM accounts ORDER BY username",
        -1, &stmt, nullptr);
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        Account acc;
        acc.id         = sqlite3_column_int64(stmt, 0);
        acc.username   = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        acc.is_admin   = sqlite3_column_int(stmt, 2) != 0;
        acc.created_at = sqlite3_column_int64(stmt, 3);
        acc.last_login = sqlite3_column_int64(stmt, 4);
        result.push_back(acc);
    }
    sqlite3_finalize(stmt);
    return result;
}

std::optional<Account> Database::authenticate(const std::string& username,
                                               const std::string& password) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_,
        "SELECT id, pw_hash, is_admin, created_at, last_login FROM accounts WHERE username=?",
        -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
    int rc = sqlite3_step(stmt);
    if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); return std::nullopt; }

    int64_t id       = sqlite3_column_int64(stmt, 0);
    std::string hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
    bool is_admin    = sqlite3_column_int(stmt, 2) != 0;
    int64_t created  = sqlite3_column_int64(stmt, 3);
    sqlite3_finalize(stmt);

    // Verify Argon2id — deliberately slow
    if (crypto_pwhash_str_verify(hash.c_str(), password.c_str(), password.size()) != 0)
        return std::nullopt;

    // Update last_login
    int64_t now = now_unix();
    sqlite3_stmt* upd = nullptr;
    sqlite3_prepare_v2(db_, "UPDATE accounts SET last_login=? WHERE id=?", -1, &upd, nullptr);
    sqlite3_bind_int64(upd, 1, now);
    sqlite3_bind_int64(upd, 2, id);
    sqlite3_step(upd);
    sqlite3_finalize(upd);

    Account acc;
    acc.id         = id;
    acc.username   = username;
    acc.is_admin   = is_admin;
    acc.created_at = created;
    acc.last_login = now;
    return acc;
}

// ── Channels ──────────────────────────────────────────────────────────────────

namespace {

void bind_audio(sqlite3_stmt* stmt, int base_idx, const voicecat::v1::AudioConfig& a) {
    sqlite3_bind_int(stmt, base_idx + 0, static_cast<int>(a.codec()));
    sqlite3_bind_int(stmt, base_idx + 1, static_cast<int>(a.mode()));
    sqlite3_bind_int(stmt, base_idx + 2, static_cast<int>(a.sample_rate()));
    sqlite3_bind_int(stmt, base_idx + 3, static_cast<int>(a.bitrate_bps()));
    sqlite3_bind_int(stmt, base_idx + 4, static_cast<int>(a.frame_ms()));
    sqlite3_bind_int(stmt, base_idx + 5, static_cast<int>(a.application()));
    sqlite3_bind_int(stmt, base_idx + 6, a.fec() ? 1 : 0);
    sqlite3_bind_int(stmt, base_idx + 7, static_cast<int>(a.expected_packet_loss()));
    sqlite3_bind_int(stmt, base_idx + 8, a.dtx() ? 1 : 0);
    sqlite3_bind_int(stmt, base_idx + 9, static_cast<int>(a.complexity()));
}

voicecat::v1::AudioConfig read_audio(sqlite3_stmt* stmt, int base_idx) {
    voicecat::v1::AudioConfig a;
    a.set_codec(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 0)));
    a.set_mode(static_cast<voicecat::v1::ChannelMode>(sqlite3_column_int(stmt, base_idx + 1)));
    a.set_sample_rate(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 2)));
    a.set_bitrate_bps(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 3)));
    a.set_frame_ms(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 4)));
    a.set_application(static_cast<voicecat::v1::OpusApplication>(sqlite3_column_int(stmt, base_idx + 5)));
    a.set_fec(sqlite3_column_int(stmt, base_idx + 6) != 0);
    a.set_expected_packet_loss(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 7)));
    a.set_dtx(sqlite3_column_int(stmt, base_idx + 8) != 0);
    a.set_complexity(static_cast<uint32_t>(sqlite3_column_int(stmt, base_idx + 9)));
    return a;
}

}  // namespace

std::optional<ChannelRecord> Database::create_channel(const ChannelRecord& ch,
                                                       const std::string& password,
                                                       std::string& error) {
    if (ch.name.empty()) { error = "channel name must not be empty"; return std::nullopt; }

    std::string pw_hash = password.empty() ? "" : hash_channel_password(password);

    sqlite3_stmt* stmt = nullptr;
    const char* sql = R"sql(
        INSERT INTO channels
        (parent_id, name, topic, password_hash, max_users, type,
         audio_codec, audio_mode, audio_sample_rate, audio_bitrate_bps, audio_frame_ms,
         audio_application, audio_fec, audio_expected_packet_loss, audio_dtx, audio_complexity,
         sort_order)
        VALUES (?,?,?,?,?,?, ?,?,?,?,?, ?,?,?,?,?, ?)
    )sql";
    int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
    if (rc != SQLITE_OK) { error = sqlite3_errmsg(db_); return std::nullopt; }

    sqlite3_bind_int(stmt, 1, static_cast<int>(ch.parent_id));
    sqlite3_bind_text(stmt, 2, ch.name.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, ch.topic.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4, pw_hash.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(stmt, 5, static_cast<int>(ch.max_users));
    sqlite3_bind_int(stmt, 6, static_cast<int>(ch.type));
    bind_audio(stmt, 7, ch.audio);  // parameters 7-16
    sqlite3_bind_int(stmt, 17, static_cast<int>(ch.sort_order));



    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return std::nullopt; }

    ChannelRecord out = ch;
    out.id = static_cast<uint32_t>(sqlite3_last_insert_rowid(db_));
    out.password_protected = !pw_hash.empty();
    return out;
}

bool Database::update_channel(const ChannelRecord& ch, const std::string& password,
                               std::string& error) {
    std::string pw_hash;
    bool update_password = false;
    if (!password.empty()) {
        pw_hash = hash_channel_password(password);
        update_password = true;
    }

    std::string sql = R"sql(
        UPDATE channels SET
            parent_id=?, name=?, topic=?, max_users=?, type=?,
            audio_codec=?, audio_mode=?, audio_sample_rate=?, audio_bitrate_bps=?, audio_frame_ms=?,
            audio_application=?, audio_fec=?, audio_expected_packet_loss=?, audio_dtx=?, audio_complexity=?,
            sort_order=?
    )sql";
    if (update_password) sql += ", password_hash=?";
    sql += " WHERE id=?";

    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
    if (rc != SQLITE_OK) { error = sqlite3_errmsg(db_); return false; }

    int idx = 1;
    sqlite3_bind_int(stmt, idx++, static_cast<int>(ch.parent_id));
    sqlite3_bind_text(stmt, idx++, ch.name.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, idx++, ch.topic.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(stmt, idx++, static_cast<int>(ch.max_users));
    sqlite3_bind_int(stmt, idx++, static_cast<int>(ch.type));
    bind_audio(stmt, idx, ch.audio); idx += 10;
    sqlite3_bind_int(stmt, idx++, static_cast<int>(ch.sort_order));
    if (update_password) {
        sqlite3_bind_text(stmt, idx++, pw_hash.c_str(), -1, SQLITE_TRANSIENT);
    }
    sqlite3_bind_int(stmt, idx++, static_cast<int>(ch.id));

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return false; }
    if (sqlite3_changes(db_) == 0) { error = "channel not found"; return false; }
    return true;
}

bool Database::delete_channel(uint32_t id, std::string& error) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "DELETE FROM channels WHERE id=?", -1, &stmt, nullptr);
    sqlite3_bind_int(stmt, 1, static_cast<int>(id));
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return false; }
    if (sqlite3_changes(db_) == 0) { error = "channel not found"; return false; }
    return true;
}

std::vector<ChannelRecord> Database::list_channels() {
    std::vector<ChannelRecord> result;
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, R"sql(
        SELECT id, parent_id, name, topic, password_hash, max_users, type,
               audio_codec, audio_mode, audio_sample_rate, audio_bitrate_bps, audio_frame_ms,
               audio_application, audio_fec, audio_expected_packet_loss, audio_dtx, audio_complexity,
               sort_order
        FROM channels
        ORDER BY sort_order, id
    )sql", -1, &stmt, nullptr);
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        ChannelRecord ch;
        ch.id                  = static_cast<uint32_t>(sqlite3_column_int(stmt, 0));
        ch.parent_id           = static_cast<uint32_t>(sqlite3_column_int(stmt, 1));
        ch.name                = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
        ch.topic               = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
        ch.password_protected  = sqlite3_column_text(stmt, 4)[0] != '\0';
        ch.max_users           = static_cast<uint32_t>(sqlite3_column_int(stmt, 5));
        ch.type                = static_cast<voicecat::v1::ChannelType>(sqlite3_column_int(stmt, 6));
        ch.audio               = read_audio(stmt, 7);
        ch.sort_order          = sqlite3_column_int(stmt, 17);
        result.push_back(std::move(ch));
    }
    sqlite3_finalize(stmt);
    return result;
}

std::optional<ChannelRecord> Database::get_channel(uint32_t id) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, R"sql(
        SELECT id, parent_id, name, topic, password_hash, max_users, type,
               audio_codec, audio_mode, audio_sample_rate, audio_bitrate_bps, audio_frame_ms,
               audio_application, audio_fec, audio_expected_packet_loss, audio_dtx, audio_complexity,
               sort_order
        FROM channels WHERE id=?
    )sql", -1, &stmt, nullptr);
    sqlite3_bind_int(stmt, 1, static_cast<int>(id));
    std::optional<ChannelRecord> result;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        ChannelRecord ch;
        ch.id                  = static_cast<uint32_t>(sqlite3_column_int(stmt, 0));
        ch.parent_id           = static_cast<uint32_t>(sqlite3_column_int(stmt, 1));
        ch.name                = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
        ch.topic               = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
        ch.password_protected  = sqlite3_column_text(stmt, 4)[0] != '\0';
        ch.max_users           = static_cast<uint32_t>(sqlite3_column_int(stmt, 5));
        ch.type                = static_cast<voicecat::v1::ChannelType>(sqlite3_column_int(stmt, 6));
        ch.audio               = read_audio(stmt, 7);
        ch.sort_order          = sqlite3_column_int(stmt, 17);
        result = std::move(ch);
    }
    sqlite3_finalize(stmt);
    return result;
}

std::string Database::hash_channel_password(const std::string& password) {
    uint8_t salt[16];
    randombytes_buf(salt, sizeof(salt));
    uint8_t hash[32];
    crypto_generichash(hash, sizeof(hash),
                       reinterpret_cast<const uint8_t*>(password.data()), password.size(),
                       salt, sizeof(salt));
    char salt_hex[33];
    char hash_hex[65];
    sodium_bin2hex(salt_hex, sizeof(salt_hex), salt, sizeof(salt));
    sodium_bin2hex(hash_hex, sizeof(hash_hex), hash, sizeof(hash));
    return std::string(salt_hex) + ":" + hash_hex;
}

bool Database::verify_channel_password(const std::string& password, const std::string& stored) {
    auto pos = stored.find(':');
    if (pos == std::string::npos || pos != 32) return false;
    std::string salt_hex = stored.substr(0, pos);
    std::string hash_hex = stored.substr(pos + 1);
    uint8_t salt[16];
    uint8_t expected[32];
    if (sodium_hex2bin(salt, sizeof(salt), salt_hex.c_str(), salt_hex.size(),
                       nullptr, nullptr, nullptr) != 0)
        return false;
    if (sodium_hex2bin(expected, sizeof(expected), hash_hex.c_str(), hash_hex.size(),
                       nullptr, nullptr, nullptr) != 0)
        return false;
    uint8_t hash[32];
    crypto_generichash(hash, sizeof(hash),
                       reinterpret_cast<const uint8_t*>(password.data()), password.size(),
                       salt, sizeof(salt));
    return sodium_memcmp(hash, expected, sizeof(hash)) == 0;
}

bool Database::check_channel_password(uint32_t id, const std::string& password) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "SELECT password_hash FROM channels WHERE id=?", -1, &stmt, nullptr);
    sqlite3_bind_int(stmt, 1, static_cast<int>(id));
    bool ok = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        const char* stored = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
        if (!stored || stored[0] == '\0') {
            ok = true;  // no password
        } else {
            ok = verify_channel_password(password, stored);
        }
    }
    sqlite3_finalize(stmt);
    return ok;
}

// ── Bans ───────────────────────────────────────────────────────────────────────

std::optional<BanRecord> Database::ban_create(const std::string& subject_type,
                                               const std::string& subject,
                                               const std::string& reason,
                                               int64_t expires_at,
                                               std::string& error) {
    if (subject_type != "user_id" && subject_type != "username" && subject_type != "ip") {
        error = "invalid subject_type";
        return std::nullopt;
    }
    if (subject.empty()) { error = "subject must not be empty"; return std::nullopt; }

    int64_t now = now_unix();
    sqlite3_stmt* stmt = nullptr;
    int rc = sqlite3_prepare_v2(db_,
        "INSERT INTO bans (subject_type, subject, reason, expires_at, created_at) VALUES (?,?,?,?,?)",
        -1, &stmt, nullptr);
    if (rc != SQLITE_OK) { error = sqlite3_errmsg(db_); return std::nullopt; }
    sqlite3_bind_text(stmt, 1, subject_type.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, subject.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, reason.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_int64(stmt, 4, expires_at);
    sqlite3_bind_int64(stmt, 5, now);
    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return std::nullopt; }

    BanRecord b;
    b.id           = sqlite3_last_insert_rowid(db_);
    b.subject_type = subject_type;
    b.subject      = subject;
    b.reason       = reason;
    b.expires_at   = expires_at;
    b.created_at   = now;
    return b;
}

bool Database::ban_remove(int64_t id, std::string& error) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_, "DELETE FROM bans WHERE id=?", -1, &stmt, nullptr);
    sqlite3_bind_int64(stmt, 1, id);
    int rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    if (rc != SQLITE_DONE) { error = sqlite3_errmsg(db_); return false; }
    if (sqlite3_changes(db_) == 0) { error = "ban not found"; return false; }
    return true;
}

bool Database::ban_check(const std::string& subject_type, const std::string& subject) {
    sqlite3_stmt* stmt = nullptr;
    sqlite3_prepare_v2(db_,
        "SELECT expires_at FROM bans WHERE subject_type=? AND subject=? AND (expires_at=0 OR expires_at>?)",
        -1, &stmt, nullptr);
    sqlite3_bind_text(stmt, 1, subject_type.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, subject.c_str(), -1, SQLITE_TRANSIENT);
    sqlite3_bind_int64(stmt, 3, now_unix());
    bool banned = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) banned = true;
    sqlite3_finalize(stmt);
    return banned;
}

std::vector<BanRecord> Database::ban_list(bool include_expired) {
    std::vector<BanRecord> result;
    sqlite3_stmt* stmt = nullptr;
    std::string sql = "SELECT id, subject_type, subject, reason, expires_at, created_at FROM bans";
    if (!include_expired) sql += " WHERE expires_at=0 OR expires_at>?";
    sql += " ORDER BY created_at DESC";
    sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr);
    if (!include_expired) sqlite3_bind_int64(stmt, 1, now_unix());
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        BanRecord b;
        b.id           = sqlite3_column_int64(stmt, 0);
        b.subject_type = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
        b.subject      = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
        b.reason       = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
        b.expires_at   = sqlite3_column_int64(stmt, 4);
        b.created_at   = sqlite3_column_int64(stmt, 5);
        result.push_back(std::move(b));
    }
    sqlite3_finalize(stmt);
    return result;
}

std::string Database::generate_password(size_t length) {
    static const char kAlphabet[] =
        "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%^&*";
    constexpr size_t kAlphaLen = sizeof(kAlphabet) - 1;
    std::string pw;
    pw.reserve(length);
    for (size_t i = 0; i < length; ++i) {
        uint8_t rnd[1];
        randombytes_buf(rnd, 1);
        pw += kAlphabet[rnd[0] % kAlphaLen];
    }
    return pw;
}

bool Database::exec(const std::string& sql, std::string& error) {
    char* errmsg = nullptr;
    int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &errmsg);
    if (rc != SQLITE_OK) {
        error = errmsg ? errmsg : "unknown error";
        if (errmsg) sqlite3_free(errmsg);
        return false;
    }
    return true;
}

int64_t Database::now_unix() const {
    using namespace std::chrono;
    return duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}

}  // namespace voicecat::server

#endif  // VOICECAT_HAS_NET
