#pragma once

#include <string>
#include <vector>
#include <mutex>
#include <cstdint>
#include <sql.h>
#include <sqlext.h>
#include <uv.h>

enum class CellType { Null, Int64, Double, String, Blob };

struct Cell {
    CellType type = CellType::Null;
    int64_t i64 = 0;
    double dbl = 0.0;
    std::string str;
    std::vector<uint8_t> blob;
};

using Row = std::vector<Cell>;
using ResultSet = std::vector<Row>;

enum class ParamType { Int, BigInt, Float, Double, NVarChar, Bit, DateTime };

struct Param {
    ParamType type;
    std::string str_val;
    int64_t int_val = 0;
    double dbl_val = 0.0;
    bool is_null = false;

    static Param null_int() { Param p; p.type = ParamType::Int; p.is_null = true; return p; }
    static Param null_bigint() { Param p; p.type = ParamType::BigInt; p.is_null = true; return p; }
    static Param null_nvarchar() { Param p; p.type = ParamType::NVarChar; p.is_null = true; return p; }
};

class OdbcPool {
public:
    struct Connection {
        SQLHDBC hdbc = SQL_NULL_HDBC;
        SQLHSTMT hstmt = SQL_NULL_HSTMT;
        bool in_use = false;
        bool in_transaction = false;
    };

    // RAII checkout guard. Returned by checkout().
    struct ConnGuard {
        Connection* conn = nullptr;
        OdbcPool* pool = nullptr;
        ConnGuard() = default;
        ConnGuard(Connection* c, OdbcPool* p) : conn(c), pool(p) {}
        ~ConnGuard() { if (conn && pool) pool->release(conn); }
        ConnGuard(const ConnGuard&) = delete;
        ConnGuard(ConnGuard&& other) noexcept : conn(other.conn), pool(other.pool) { other.conn = nullptr; }
        ConnGuard& operator=(ConnGuard&& other) noexcept {
            if (this != &other) {
                if (conn && pool) pool->release(conn);
                conn = other.conn;
                pool = other.pool;
                other.conn = nullptr;
            }
            return *this;
        }
        Connection* operator->() const { return conn; }
        Connection* get() const { return conn; }
        explicit operator bool() const { return conn != nullptr; }
    };

    ~OdbcPool();
    int connect();
    void disconnect();

    bool execute(const std::string& sql, const std::vector<Param>& params, ResultSet& out);
    bool execute(const std::string& sql, ResultSet& out);
    int64_t execute_scalar(const std::string& sql, const std::vector<Param>& params = {}, int64_t fallback = 0);

    // Transactional execution on an explicitly checked-out connection.
    ConnGuard checkout();
    bool execute(Connection* conn, const std::string& sql, const std::vector<Param>& params, ResultSet& out);
    bool execute(Connection* conn, const std::string& sql, ResultSet& out);
    bool begin(Connection* conn);
    bool commit(Connection* conn);
    bool rollback(Connection* conn);

    void release(Connection* c);

private:
    SQLHENV henv_ = SQL_NULL_HENV;
    std::vector<Connection> conns_;
    std::mutex mutex_;
    Connection* checkout_raw();
};

OdbcPool& get_pool();
