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

pub const Options = struct {
    json: bool = false,
    verbose: bool = false,
    task_id: ?[]const u8 = null,
};

pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
    var flags = core.flags.CommonFlags{};
    var commit_hex: ?[]const u8 = null;
    var task_id: ?[]const u8 = null;

    var i: usize = 0;
    while (i < args.len) : (i += 1) {
        const arg = args[i];
        if (std.mem.eql(u8, arg, "--json")) {
            flags.json = true;
        } else if (std.mem.eql(u8, arg, "--verbose")) {
            flags.verbose = true;
        } else if (std.mem.eql(u8, arg, "--task") and i + 1 < args.len) {
            task_id = args[i + 1];
            i += 1;
        } else if (std.mem.startsWith(u8, arg, "--help")) {
            return printUsage();
        } else if (std.mem.startsWith(u8, arg, "--")) {
            core.output.err("Unknown option");
            return error.InvalidArgs;
        } else {
            commit_hex = arg;
        }
    }

    core.output.setMode(if (flags.json) .json else .text);

    const config = try Config.load(allocator);
    defer {
        var mut_config = config;
        mut_config.deinit(allocator);
    }

    if (config.api_key.len == 0) return error.APIKeyMissing;

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

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

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

    if (task_id) |tid| {
        try client.sendValidateRequestTask(api_key_hash, tid);
    } else {
        if (commit_hex == null) {
            core.output.err("No commit hash specified");
            return printUsage();
        } else if (commit_hex.?.len != 40) {
            std.debug.print("validate requires a 40-char commit id (or --task <task_id>)\n", .{});
            try printUsage();
            return error.InvalidArgs;
        }
        const commit_bytes = try crypto.decodeHex(allocator, commit_hex.?);
        defer allocator.free(commit_bytes);
        if (commit_bytes.len != 20) return error.InvalidCommitId;
        try client.sendValidateRequestCommit(api_key_hash, commit_bytes);
    }

    // Expect Data packet with data_type "validate" and JSON payload.
    const msg = try client.receiveMessage(allocator);
    defer allocator.free(msg);

    const packet = protocol.ResponsePacket.deserialize(msg, allocator) catch {
        std.debug.print("{s}\n", .{msg});
        return error.InvalidPacket;
    };
    defer packet.deinit(allocator);

    if (packet.packet_type == .error_packet) {
        try client.handleResponsePacket(packet, "validate");
        return error.ValidationFailed;
    }

    if (packet.packet_type != .data or packet.data_payload == null) {
        std.debug.print("unexpected response for validate\n", .{});
        return error.InvalidPacket;
    }

    const payload = packet.data_payload.?;
    const parsed = try std.json.parseFromSlice(std.json.Value, allocator, payload, .{});
    defer parsed.deinit();

    if (flags.json) {
        try io.stdoutWriteJson(parsed.value);
    } else {
        const root = parsed.value.object;
        const ok_val = root.get("ok") orelse return error.InvalidPacket;
        if (ok_val != .bool) return error.InvalidPacket;
        _ = ok_val.bool;

        if (root.get("commit_id")) |cid| {
            if (cid != .null) {
                std.debug.print("commit_id: {s}\n", .{cid.string});
            }
        }
        if (root.get("task_id")) |tid| {
            if (tid != .null) {
                std.debug.print("task_id: {s}\n", .{tid.string});
            }
        }

        if (root.get("checks")) |checks_val| {
            if (checks_val == .object) {
                if (flags.verbose) {
                    std.debug.print("checks:\n", .{});
                } else {
                    std.debug.print("failed_checks:\n", .{});
                }

                var it = checks_val.object.iterator();
                var any_failed: bool = false;
                while (it.next()) |entry| {
                    const name = entry.key_ptr.*;
                    const check_val = entry.value_ptr.*;
                    if (check_val != .object) continue;

                    const check_obj = check_val.object;
                    var check_ok: bool = false;
                    if (check_obj.get("ok")) |cok| {
                        if (cok == .bool) check_ok = cok.bool;
                    }

                    if (!check_ok) any_failed = true;
                    if (!flags.verbose and check_ok) continue;

                    if (check_ok) {
                        std.debug.print("- {s}: OK\n", .{name});
                    } else {
                        std.debug.print("- {s}: FAILED\n", .{name});
                    }

                    if (flags.verbose or !check_ok) {
                        if (check_obj.get("expected")) |exp| {
                            if (exp != .null) {
                                std.debug.print("  expected: {s}\n", .{exp.string});
                            }
                        }
                        if (check_obj.get("actual")) |act| {
                            if (act != .null) {
                                std.debug.print("  actual:   {s}\n", .{act.string});
                            }
                        }
                        if (check_obj.get("details")) |det| {
                            if (det != .null) {
                                std.debug.print("  details:  {s}\n", .{det.string});
                            }
                        }
                    }
                }

                if (!flags.verbose and !any_failed) {
                    std.debug.print("- none\n", .{});
                }
            }
        }

        return;
    }

    return;
}

fn printUsage() !void {
    std.debug.print("Usage:\n", .{});
    std.debug.print("\tml validate <commit_id> [--json] [--verbose]\n", .{});
    std.debug.print("\tml validate --task <task_id> [--json] [--verbose]\n", .{});
}
