const std = @import("std");

// SQLite C bindings
pub const c = @cImport({
    @cInclude("sqlite3.h");
});

// Public type alias for prepared statement
pub const Stmt = ?*c.sqlite3_stmt;

// SQLITE_TRANSIENT constant - use C wrapper to avoid Zig 0.15 C translation issue
extern fn fetchml_sqlite_transient() c.sqlite3_destructor_type;
fn sqliteTransient() c.sqlite3_destructor_type {
    return fetchml_sqlite_transient();
}

// Schema for ML tracking tables
const SCHEMA =
    \\ CREATE TABLE IF NOT EXISTS ml_experiments (
    \\     experiment_id TEXT PRIMARY KEY,
    \\     name TEXT NOT NULL,
    \\     artifact_path TEXT,
    \\     lifecycle TEXT DEFAULT 'active',
    \\     created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    \\ );
    \\ CREATE TABLE IF NOT EXISTS ml_runs (
    \\     run_id TEXT PRIMARY KEY,
    \\     experiment_id TEXT REFERENCES ml_experiments(experiment_id),
    \\     name TEXT,
    \\     status TEXT,       -- RUNNING, FINISHED, FAILED, CANCELLED
    \\     start_time DATETIME,
    \\     end_time DATETIME,
    \\     artifact_uri TEXT,
    \\     pid INTEGER DEFAULT NULL,
    \\     synced INTEGER DEFAULT 0
    \\ );
    \\ CREATE TABLE IF NOT EXISTS ml_metrics (
    \\     run_id TEXT REFERENCES ml_runs(run_id),
    \\     key TEXT,
    \\     value REAL,
    \\     step INTEGER DEFAULT 0,
    \\     timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
    \\ );
    \\ CREATE TABLE IF NOT EXISTS ml_params (
    \\     run_id TEXT REFERENCES ml_runs(run_id),
    \\     key TEXT,
    \\     value TEXT
    \\ );
    \\ CREATE TABLE IF NOT EXISTS ml_tags (
    \\     run_id TEXT REFERENCES ml_runs(run_id),
    \\     key TEXT,
    \\     value TEXT
    \\ );
;

/// Database connection handle
pub const DB = struct {
    handle: ?*c.sqlite3,
    path: []const u8,

    /// Initialize database with WAL mode and schema
    pub fn init(allocator: std.mem.Allocator, db_path: []const u8) !DB {
        var db: ?*c.sqlite3 = null;

        // Open database
        const rc = c.sqlite3_open(db_path.ptr, &db);
        if (rc != c.SQLITE_OK) {
            std.log.err("Failed to open database: {s}", .{c.sqlite3_errmsg(db)});
            return error.DBOpenFailed;
        }

        // Enable WAL mode - required for concurrent CLI writes and TUI reads
        var errmsg: [*c]u8 = null;
        _ = c.sqlite3_exec(db, "PRAGMA journal_mode=WAL;", null, null, &errmsg);
        if (errmsg != null) {
            c.sqlite3_free(errmsg);
        }

        // Set synchronous=NORMAL for performance under WAL
        _ = c.sqlite3_exec(db, "PRAGMA synchronous=NORMAL;", null, null, &errmsg);
        if (errmsg != null) {
            c.sqlite3_free(errmsg);
        }

        // Apply schema
        _ = c.sqlite3_exec(db, SCHEMA, null, null, &errmsg);
        if (errmsg != null) {
            std.log.err("Schema creation failed: {s}", .{errmsg});
            c.sqlite3_free(errmsg);
            _ = c.sqlite3_close(db);
            return error.SchemaFailed;
        }

        const path_copy = try allocator.dupe(u8, db_path);

        return DB{
            .handle = db,
            .path = path_copy,
        };
    }

    /// Close database connection
    pub fn close(self: *DB) void {
        if (self.handle) |db| {
            _ = c.sqlite3_close(db);
            self.handle = null;
        }
    }

    /// Checkpoint WAL on clean shutdown
    pub fn checkpointOnExit(self: *DB) void {
        if (self.handle) |db| {
            var errmsg: [*c]u8 = null;
            _ = c.sqlite3_exec(db, "PRAGMA wal_checkpoint(TRUNCATE);", null, null, &errmsg);
            if (errmsg != null) {
                c.sqlite3_free(errmsg);
            }
        }
    }

    /// Execute a simple SQL statement
    pub fn exec(self: DB, sql: []const u8) !void {
        if (self.handle == null) return error.DBNotOpen;

        var errmsg: [*c]u8 = null;
        const rc = c.sqlite3_exec(self.handle, sql.ptr, null, null, &errmsg);

        if (rc != c.SQLITE_OK) {
            if (errmsg) |e| {
                std.log.err("SQL error: {s}", .{e});
                c.sqlite3_free(errmsg);
            }
            return error.SQLExecFailed;
        }
    }

    /// Prepare a statement
    pub fn prepare(self: DB, sql: []const u8) !?*c.sqlite3_stmt {
        if (self.handle == null) return error.DBNotOpen;

        var stmt: ?*c.sqlite3_stmt = null;
        const rc = c.sqlite3_prepare_v2(self.handle, sql.ptr, @intCast(sql.len), &stmt, null);

        if (rc != c.SQLITE_OK) {
            std.log.err("Prepare failed: {s}", .{c.sqlite3_errmsg(self.handle)});
            return error.PrepareFailed;
        }

        return stmt;
    }

    /// Finalize a prepared statement
    pub fn finalize(stmt: ?*c.sqlite3_stmt) void {
        if (stmt) |s| {
            _ = c.sqlite3_finalize(s);
        }
    }

    /// Bind text parameter to statement
    pub fn bindText(stmt: ?*c.sqlite3_stmt, idx: i32, value: []const u8) !void {
        if (stmt == null) return error.InvalidStatement;
        const rc = c.sqlite3_bind_text(stmt, idx, value.ptr, @intCast(value.len), sqliteTransient());
        if (rc != c.SQLITE_OK) return error.BindFailed;
    }

    /// Bind int64 parameter to statement
    pub fn bindInt64(stmt: ?*c.sqlite3_stmt, idx: i32, value: i64) !void {
        if (stmt == null) return error.InvalidStatement;
        const rc = c.sqlite3_bind_int64(stmt, idx, value);
        if (rc != c.SQLITE_OK) return error.BindFailed;
    }

    /// Bind double parameter to statement
    pub fn bindDouble(stmt: ?*c.sqlite3_stmt, idx: i32, value: f64) !void {
        if (stmt == null) return error.InvalidStatement;
        const rc = c.sqlite3_bind_double(stmt, idx, value);
        if (rc != c.SQLITE_OK) return error.BindFailed;
    }

    /// Step statement (execute)
    pub fn step(stmt: ?*c.sqlite3_stmt) !bool {
        if (stmt == null) return error.InvalidStatement;
        const rc = c.sqlite3_step(stmt);
        return rc == c.SQLITE_ROW; // true if has row, false if done
    }

    /// Reset statement for reuse
    pub fn reset(stmt: ?*c.sqlite3_stmt) !void {
        if (stmt == null) return error.InvalidStatement;
        _ = c.sqlite3_reset(stmt);
        _ = c.sqlite3_clear_bindings(stmt);
    }

    /// Get column text
    pub fn columnText(stmt: ?*c.sqlite3_stmt, idx: i32) []const u8 {
        if (stmt == null) return "";
        const ptr = c.sqlite3_column_text(stmt, idx);
        const len = c.sqlite3_column_bytes(stmt, idx);
        if (ptr == null or len == 0) return "";
        return ptr[0..@intCast(len)];
    }

    /// Get column int64
    pub fn columnInt64(stmt: ?*c.sqlite3_stmt, idx: i32) i64 {
        if (stmt == null) return 0;
        return c.sqlite3_column_int64(stmt, idx);
    }

    /// Get column double
    pub fn columnDouble(stmt: ?*c.sqlite3_stmt, idx: i32) f64 {
        if (stmt == null) return 0.0;
        return c.sqlite3_column_double(stmt, idx);
    }
};

/// Generate UUID v4 (simple random-based)
pub fn generateUUID(allocator: std.mem.Allocator) ![]const u8 {
    var buf: [36]u8 = undefined;
    const hex_chars = "0123456789abcdef";

    // Random bytes (simplified - in production use crypto RNG)
    var bytes: [16]u8 = undefined;
    std.crypto.random.bytes(&bytes);

    // Set version (4) and variant bits
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;

    // Format as UUID string
    var idx: usize = 0;
    for (0..16) |i| {
        if (i == 4 or i == 6 or i == 8 or i == 10) {
            buf[idx] = '-';
            idx += 1;
        }
        buf[idx] = hex_chars[bytes[i] >> 4];
        buf[idx + 1] = hex_chars[bytes[i] & 0x0f];
        idx += 2;
    }

    return try allocator.dupe(u8, &buf);
}

/// Get current timestamp as ISO8601 string
pub fn currentTimestamp(allocator: std.mem.Allocator) ![]const u8 {
    const now = std.time.timestamp();
    const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = @intCast(now) };
    const epoch_day = epoch_seconds.getEpochDay();
    const year_day = epoch_day.calculateYearDay();
    const month_day = year_day.calculateMonthDay();
    const day_seconds = epoch_seconds.getDaySeconds();

    var buf: [20]u8 = undefined;
    const len = try std.fmt.bufPrint(&buf, "{d:0>4}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}", .{
        year_day.year,
        month_day.month.numeric(),
        month_day.day_index + 1,
        day_seconds.getHoursIntoDay(),
        day_seconds.getMinutesIntoHour(),
        day_seconds.getSecondsIntoMinute(),
    });

    return try allocator.dupe(u8, len);
}
