const std = @import("std");
const ws = @import("../net/ws/client.zig");
const protocol = @import("../net/protocol.zig");
const config = @import("../config.zig");
const crypto = @import("../utils/crypto.zig");
const core = @import("../core.zig");

/// Log a metric to server mode
pub fn logMetric(
    allocator: std.mem.Allocator,
    commit_id: []const u8,
    name: []const u8,
    value: f64,
    step: u32,
    json: bool,
) !void {
    const cfg = try config.Config.load(allocator);
    defer {
        var mut_cfg = cfg;
        mut_cfg.deinit(allocator);
    }

    const api_key_hash = try crypto.hashApiKey(allocator, cfg.api_key);
    defer allocator.free(api_key_hash);

    const ws_url = try cfg.getWebSocketUrl(allocator);
    defer allocator.free(ws_url);

    var client = try ws.Client.connect(allocator, ws_url, cfg.api_key);
    defer client.close();

    try client.sendLogMetric(api_key_hash, commit_id, name, value, step);

    if (json) {
        const message = try client.receiveMessage(allocator);
        defer allocator.free(message);

        const packet = protocol.ResponsePacket.deserialize(message, allocator) catch {
            std.debug.print(
                "{{\"success\":true,\"command\":\"experiment.log\",\"data\":{{\"commit_id\":\"{s}\",\"metric\":{{\"name\":\"{s}\",\"value\":{d},\"step\":{d}}},\"message\":\"{s}\"}}}}\n",
                .{ commit_id, name, value, step, message },
            );
            return;
        };
        defer packet.deinit(allocator);

        switch (packet.packet_type) {
            .success => {
                std.debug.print(
                    "{{\"success\":true,\"command\":\"experiment.log\",\"data\":{{\"commit_id\":\"{s}\",\"metric\":{{\"name\":\"{s}\",\"value\":{d},\"step\":{d}}},\"message\":\"{s}\"}}}}\n",
                    .{ commit_id, name, value, step, message },
                );
                return;
            },
            else => {},
        }
    } else {
        try client.receiveAndHandleResponse(allocator, "Log metric");
        std.debug.print("Metric logged successfully!\n", .{});
        std.debug.print("Commit ID: {s}\n", .{commit_id});
        std.debug.print("Metric: {s} = {d:.4} (step {d})\n", .{ name, value, step });
    }
}

/// List experiments from server mode
pub fn list(allocator: std.mem.Allocator, json: bool) !void {
    const entries = @import("../utils/history.zig").loadEntries(allocator) catch |err| {
        if (json) {
            const details = try std.fmt.allocPrint(allocator, "{}", .{err});
            defer allocator.free(details);
            core.output.errorMsgDetailed("experiment.list", "Failed to read experiment history", details);
        } else {
            std.log.err("Failed to read experiment history: {}", .{err});
        }
        return err;
    };
    defer @import("../utils/history.zig").freeEntries(allocator, entries);

    if (entries.len == 0) {
        if (json) {
            std.debug.print("{{\"success\":true,\"command\":\"experiment.list\",\"data\":{{\"experiments\":[],\"total\":0,\"message\":\"No experiments recorded yet. Use `ml queue` to submit one.\"}}}}\n", .{});
        } else {
            std.debug.print("No experiments recorded yet. Use `ml queue` to submit one.\n", .{});
        }
        return;
    }

    if (json) {
        std.debug.print("{{\"success\":true,\"command\":\"experiment.list\",\"data\":{{\"experiments\":[", .{});
        var idx: usize = 0;
        while (idx < entries.len) : (idx += 1) {
            const entry = entries[entries.len - idx - 1];
            if (idx > 0) {
                std.debug.print(",", .{});
            }
            std.debug.print(
                "{{\"alias\":\"{s}\",\"commit_id\":\"{s}\",\"queued_at\":{d}}}",
                .{
                    entry.job_name,
                    entry.commit_id,
                    entry.queued_at,
                },
            );
        }
        std.debug.print("],\"total\":{d}", .{entries.len});
        std.debug.print("}}}}\n", .{});
    } else {
        std.debug.print("\nRecent Experiments (latest first):\n", .{});
        std.debug.print("---------------------------------\n", .{});

        const max_display = if (entries.len > 20) 20 else entries.len;
        var idx: usize = 0;
        while (idx < max_display) : (idx += 1) {
            const entry = entries[entries.len - idx - 1];
            std.debug.print("{d:2}) Alias: {s}\n", .{ idx + 1, entry.job_name });
            std.debug.print("\t\tCommit: {s}\n", .{entry.commit_id});
            std.debug.print("\t\tQueued: {d}\n\n", .{entry.queued_at});
        }

        if (entries.len > max_display) {
            std.debug.print("...and {d} more\n", .{entries.len - max_display});
        }
    }
}
