/*
 * server/db.h — SQLite-backed account store with Argon2id password hashing.
 *
 * All password operations (hash + verify) are intentionally slow via Argon2id.
 * Call authenticate() from a WorkerPool thread, never from the net thread.
 *
 * Design: docs/security.md §4.
 */
#ifndef VOICECAT_SERVER_DB_H
#define VOICECAT_SERVER_DB_H

#ifdef VOICECAT_HAS_NET

#include <cstdint>
#include <optional>
#include <string>
#include <vector>

#include "proto/voicecat.pb.h"

struct sqlite3;

namespace voicecat::server {

struct Account {
    int64_t     id{};
    std::string username;
    bool        is_admin{false};
    int64_t     created_at{};
    int64_t     last_login{};
};

// Persistent channel record. Mirrors voicecat::v1::Channel where applicable.
struct ChannelRecord {
    uint32_t                 id{0};
    uint32_t                 parent_id{0};          // 0 = root
    std::string              name;
    std::string              topic;
    bool                     password_protected{false};
    uint32_t                 max_users{0};            // 0 = unlimited
    voicecat::v1::ChannelType type{voicecat::v1::CHANNEL_PERMANENT};
    voicecat::v1::AudioConfig audio;
    int32_t                  sort_order{0};
};

// Persistent ban record.
struct BanRecord {
    int64_t     id{0};
    std::string subject_type;  // "user_id" or "ip"
    std::string subject;       // the banned value
    std::string reason;
    int64_t     expires_at{0}; // 0 = permanent
    int64_t     created_at{0};
};

class Database {
   public:
    explicit Database(std::string path);
    ~Database();

    Database(const Database&) = delete;
    Database& operator=(const Database&) = delete;

    // Open the database, run migrations, create tables if needed.
    // Returns false + sets error on failure.
    bool open(std::string& error);

    // True if the accounts table has no rows.
    bool is_empty();

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

    // Create a new account. Hashes password with Argon2id. Thread-safe.
    std::optional<Account> create_account(const std::string& username,
                                          const std::string& password,
                                          bool is_admin, std::string& error);

    // Reset an existing account's password. Thread-safe.
    bool reset_password(const std::string& username, const std::string& new_password,
                        std::string& error);

    // Delete an account. Thread-safe.
    bool delete_account(const std::string& username, std::string& error);

    // List all accounts (no passwords). Thread-safe.
    std::vector<Account> list_accounts();

    // Verify username + password. Updates last_login on success.
    // Blocking (Argon2id) — must be called from a WorkerPool thread.
    std::optional<Account> authenticate(const std::string& username,
                                        const std::string& password);

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

    // Create a new channel. `password` may be empty. Returns the persisted record
    // (with assigned id), or nullopt on error. Thread-safe.
    std::optional<ChannelRecord> create_channel(const ChannelRecord& ch,
                                                const std::string& password,
                                                std::string& error);

    // Update an existing channel. Returns false if not found or on error.
    bool update_channel(const ChannelRecord& ch, const std::string& password,
                        std::string& error);

    // Delete a channel. Returns false if not found or on error.
    bool delete_channel(uint32_t id, std::string& error);

    // Load all channels. Thread-safe.
    std::vector<ChannelRecord> list_channels();

    // Fetch a single channel. Thread-safe.
    std::optional<ChannelRecord> get_channel(uint32_t id);

    // Check a plaintext channel password. Returns true if the channel has no
    // password or if the password matches. Fast (BLAKE2b), safe for net thread.
    bool check_channel_password(uint32_t id, const std::string& password);

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

    // Ban a subject (user_id or ip). Returns the persisted record, or nullopt.
    std::optional<BanRecord> ban_create(const std::string& subject_type,
                                        const std::string& subject,
                                        const std::string& reason,
                                        int64_t expires_at,
                                        std::string& error);

    // Remove a ban by id. Returns false if not found or on error.
    bool ban_remove(int64_t id, std::string& error);

    // Check whether a subject is currently banned. Thread-safe.
    bool ban_check(const std::string& subject_type, const std::string& subject);

    // List active bans (optionally all). Thread-safe.
    std::vector<BanRecord> ban_list(bool include_expired = false);

    // Generate a random printable password of the given length.
    static std::string generate_password(size_t length = 20);

   private:
    bool exec(const std::string& sql, std::string& error);
    bool migrate(std::string& error);
    int64_t now_unix() const;

    // Channel password hashing (fast, net-thread-safe).
    static std::string hash_channel_password(const std::string& password);
    static bool verify_channel_password(const std::string& password,
                                        const std::string& stored);

    std::string path_;
    sqlite3*    db_{nullptr};
};

}  // namespace voicecat::server

#endif  // VOICECAT_HAS_NET
#endif  // VOICECAT_SERVER_DB_H
