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

/// Logs command - fetch or stream run logs
/// Usage:
///   ml logs <run_id>              # Fetch logs from local file or server
///   ml logs <run_id> --follow     # Stream logs from server
pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
    var flags: core.flags.CommonFlags = .{};

    var command_args = try core.flags.parseCommon(allocator, args, &flags);
    defer command_args.deinit(allocator);

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

    if (flags.help) {
        return printUsage();
    }

    if (command_args.items.len < 1) {
        std.log.err("Usage: ml logs <run_id> [--follow]", .{});
        return error.MissingArgument;
    }

    const target = command_args.items[0];
    const follow = core.flags.parseBoolFlag(command_args.items, "follow");

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

    // Detect mode
    const mode_result = try mode.detect(allocator, cfg);
    if (mode_result.warning) |w| {
        std.log.warn("{s}", .{w});
    }

    if (mode.isOffline(mode_result.mode)) {
        // Local mode: read from output.log file
        return try fetchLocalLogs(allocator, target, &cfg, flags.json);
    } else {
        // Online mode: fetch or stream from server
        if (follow) {
            return try streamServerLogs(allocator, target, cfg);
        } else {
            return try fetchServerLogs(allocator, target, cfg);
        }
    }
}

fn fetchLocalLogs(allocator: std.mem.Allocator, target: []const u8, cfg: *const config.Config, json: bool) !void {
    // Resolve manifest path
    const manifest_path = manifest_lib.resolveManifestPath(target, cfg.artifact_path, allocator) catch |err| {
        if (err == error.ManifestNotFound) {
            std.log.err("Run not found: {s}", .{target});
            return error.RunNotFound;
        }
        return err;
    };
    defer allocator.free(manifest_path);

    // Read manifest to get artifact path
    var manifest = try manifest_lib.readManifest(manifest_path, allocator);
    defer manifest.deinit(allocator);

    // Build output.log path
    const output_path = try std.fs.path.join(allocator, &[_][]const u8{
        manifest.artifact_path,
        "output.log",
    });
    defer allocator.free(output_path);

    // Read output.log
    const content = std.fs.cwd().readFileAlloc(allocator, output_path, 10 * 1024 * 1024) catch |err| {
        if (err == error.FileNotFound) {
            std.log.err("No logs found for run: {s}", .{target});
            return error.LogsNotFound;
        }
        return err;
    };
    defer allocator.free(content);

    if (json) {
        // Escape content for JSON
        var escaped: std.ArrayList(u8) = .empty;
        defer escaped.deinit(allocator);
        const writer = escaped.writer(allocator);

        for (content) |c| {
            switch (c) {
                '\\' => try writer.writeAll("\\\\"),
                '"' => try writer.writeAll("\\\""),
                '\n' => try writer.writeAll("\\n"),
                '\r' => try writer.writeAll("\\r"),
                '\t' => try writer.writeAll("\\t"),
                else => {
                    if (c >= 0x20 and c < 0x7f) {
                        try writer.writeByte(c);
                    } else {
                        try writer.print("\\u{x:0>4}", .{c});
                    }
                },
            }
        }

        std.debug.print("{{\"success\":true,\"run_id\":\"{s}\",\"logs\":\"{s}\"}}\n", .{
            manifest.run_id,
            escaped.items,
        });
    } else {
        std.debug.print("{s}\n", .{content});
    }
}

fn fetchServerLogs(allocator: std.mem.Allocator, target: []const u8, cfg: config.Config) !void {
    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.sendGetLogs(target, api_key_hash);

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

    std.debug.print("{s}\n", .{message});
}

fn streamServerLogs(allocator: std.mem.Allocator, target: []const u8, cfg: config.Config) !void {
    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();

    std.debug.print("Streaming logs for: {s}\n", .{target});

    try client.sendStreamLogs(target, api_key_hash);

    // Stream loop
    while (true) {
        const message = try client.receiveMessage(allocator);
        defer allocator.free(message);

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

        switch (packet.packet_type) {
            .data => {
                if (packet.data_payload) |payload| {
                    std.debug.print("{s}\n", .{payload});
                }
            },
            .error_packet => {
                const err_msg = packet.error_message orelse "Stream error";
                core.output.err(err_msg);
                return error.ServerError;
            },
            else => {},
        }
    }
}

fn printUsage() !void {
    std.debug.print("Usage: ml logs <run_id> [options]\n\n", .{});
    std.debug.print("Fetch or stream run logs.\n\n", .{});
    std.debug.print("Options:\n", .{});
    std.debug.print("\t--follow, -f\tStream logs from server (online mode)\n", .{});
    std.debug.print("\t--help, -h\tShow this help message\n", .{});
    std.debug.print("\t--json\t\tOutput structured JSON\n\n", .{});
    std.debug.print("Examples:\n", .{});
    std.debug.print("\tml logs abc123\t\t# Fetch logs (local or server)\n", .{});
    std.debug.print("\tml logs abc123 --follow\t# Stream logs from server\n", .{});
}
