const std = @import("std");
const db = @import("../db.zig");
const config = @import("../config.zig");
const core = @import("../core.zig");

pub const Experiment = struct {
    id: []const u8,
    name: []const u8,
    lifecycle: []const u8,
    created: []const u8,
};

/// Create a new experiment in local mode
pub fn create(allocator: std.mem.Allocator, name: []const u8, artifact_path: ?[]const u8, json: bool) !void {
    // Load config
    const cfg = try config.Config.load(allocator);
    defer {
        var mut_cfg = cfg;
        mut_cfg.deinit(allocator);
    }

    if (!cfg.isLocalMode()) {
        if (json) {
            core.output.errorMsg("experiment.create", "create only works in local mode (sqlite://)");
        } else {
            std.log.err("Error: experiment create only works in local mode (sqlite://)", .{});
        }
        return error.NotLocalMode;
    }

    // Get DB path
    const db_path = try cfg.getDBPath(allocator);
    defer allocator.free(db_path);

    // Initialize DB
    var database = try db.DB.init(allocator, db_path);
    defer database.close();

    // Generate experiment ID
    const exp_id = try db.generateUUID(allocator);
    defer allocator.free(exp_id);

    // Insert experiment
    const sql = "INSERT INTO ml_experiments (experiment_id, name, artifact_path) VALUES (?, ?, ?);";
    const stmt = try database.prepare(sql);
    defer db.DB.finalize(stmt);

    try db.DB.bindText(stmt, 1, exp_id);
    try db.DB.bindText(stmt, 2, name);
    try db.DB.bindText(stmt, 3, artifact_path orelse "");

    _ = try db.DB.step(stmt);
    database.checkpointOnExit();

    if (json) {
        std.debug.print("{{\"success\":true,\"command\":\"experiment.create\",\"data\":{{\"experiment_id\":\"{s}\",\"name\":\"{s}\"}}}}\n", .{ exp_id, name });
    } else {
        std.debug.print("Created experiment: {s} (ID: {s})\n", .{ name, exp_id });
    }
}

/// Log a metric for a run in local mode
pub fn logMetric(
    allocator: std.mem.Allocator,
    run_id: []const u8,
    name: []const u8,
    value: f64,
    step: i64,
    json: bool,
) !void {
    // Load config
    const cfg = try config.Config.load(allocator);
    defer {
        var mut_cfg = cfg;
        mut_cfg.deinit(allocator);
    }

    // Get DB path
    const db_path = try cfg.getDBPath(allocator);
    defer allocator.free(db_path);

    // Initialize DB
    var database = try db.DB.init(allocator, db_path);
    defer database.close();

    // Insert metric
    const sql = "INSERT INTO ml_metrics (run_id, key, value, step) VALUES (?, ?, ?, ?);";
    const stmt = try database.prepare(sql);
    defer db.DB.finalize(stmt);

    try db.DB.bindText(stmt, 1, run_id);
    try db.DB.bindText(stmt, 2, name);
    try db.DB.bindDouble(stmt, 3, value);
    try db.DB.bindInt64(stmt, 4, step);

    _ = try db.DB.step(stmt);

    if (json) {
        std.debug.print("{{\"success\":true,\"command\":\"experiment.log\",\"data\":{{\"run_id\":\"{s}\",\"metric\":{{\"name\":\"{s}\",\"value\":{d},\"step\":{d}}}}}}}\n", .{ run_id, name, value, step });
    } else {
        std.debug.print("Logged metric: {s} = {d:.4} (step {d})\n", .{ name, value, step });
    }
}

/// List all experiments in local mode
pub fn list(allocator: std.mem.Allocator, json: bool) !void {
    // Load config
    const cfg = try config.Config.load(allocator);
    defer {
        var mut_cfg = cfg;
        mut_cfg.deinit(allocator);
    }

    // Get DB path
    const db_path = try cfg.getDBPath(allocator);
    defer allocator.free(db_path);

    // Initialize DB
    var database = try db.DB.init(allocator, db_path);
    defer database.close();

    // Query experiments
    const sql = "SELECT experiment_id, name, lifecycle, created_at FROM ml_experiments ORDER BY created_at DESC;";
    const stmt = try database.prepare(sql);
    defer db.DB.finalize(stmt);

    var experiments = std.ArrayList(Experiment).initCapacity(allocator, 10) catch |err| {
        return err;
    };
    defer {
        for (experiments.items) |exp| {
            allocator.free(exp.id);
            allocator.free(exp.name);
            allocator.free(exp.lifecycle);
            allocator.free(exp.created);
        }
        experiments.deinit(allocator);
    }

    while (try db.DB.step(stmt)) {
        const id = try allocator.dupe(u8, db.DB.columnText(stmt, 0));
        const name = try allocator.dupe(u8, db.DB.columnText(stmt, 1));
        const lifecycle = try allocator.dupe(u8, db.DB.columnText(stmt, 2));
        const created = try allocator.dupe(u8, db.DB.columnText(stmt, 3));
        try experiments.append(allocator, .{ .id = id, .name = name, .lifecycle = lifecycle, .created = created });
    }

    if (json) {
        std.debug.print("{{\"success\":true,\"command\":\"experiment.list\",\"data\":{{\"experiments\":[", .{});
        for (experiments.items, 0..) |exp, idx| {
            if (idx > 0) std.debug.print(",", .{});
            std.debug.print("{{\"experiment_id\":\"{s}\",\"name\":\"{s}\",\"lifecycle\":\"{s}\",\"created_at\":\"{s}\"}}", .{ exp.id, exp.name, exp.lifecycle, exp.created });
        }
        std.debug.print("],\"total\":{d}}}}}\n", .{experiments.items.len});
    } else {
        if (experiments.items.len == 0) {
            std.debug.print("No experiments found. Create one with: ml experiment create --name <name>\n", .{});
        } else {
            std.debug.print("\nExperiments:\n", .{});
            std.debug.print("{s:-<60}\n", .{""});
            for (experiments.items) |exp| {
                std.debug.print("{s} | {s} | {s} | {s}\n", .{ exp.id, exp.name, exp.lifecycle, exp.created });
            }
            std.debug.print("\nTotal: {d} experiments\n", .{experiments.items.len});
        }
    }
}
