//! OpenCode Go usage feed implementation.
//!
//! Mirrors prime-agent:packages/ai/src/utils/usage/providers/opencode-go.ts field for field.
const std = @import("std");
const usage = @import("../usage.zig");
const iso8601 = @import("../iso8601.zig");

pub const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";

const WindowDef = struct {
    key: []const u8,
    label: []const u8,
    windowLabel: []const u8,
    windowSeconds: i64,
};

const OPENCODE_WINDOWS = [_]WindowDef{
    .{ .key = "rolling", .label = "Session", .windowLabel = "5h window", .windowSeconds = 5 * 3600 },
    .{ .key = "weekly", .label = "Weekly", .windowLabel = "7d window", .windowSeconds = 7 * 86_400 },
    .{ .key = "monthly", .label = "Monthly", .windowLabel = "30d window", .windowSeconds = 30 * 86_400 },
};

pub fn parseOpenCodeGoUsage(alloc: std.mem.Allocator, payload: ?std.json.Value) ![]const usage.UsageLimit {
    const root = usage.asObject(payload);
    const usage_obj = if (root) |r| usage.asObject(r.get("usage")) else null;
    if (usage_obj == null) return &[_]usage.UsageLimit{};

    var limits = std.ArrayList(usage.UsageLimit).empty;

    for (OPENCODE_WINDOWS) |win| {
        const raw_window = usage.asObject(usage_obj.?.get(win.key)) orelse continue;

        const used_percent = usage.asNumber(raw_window.get("percent"));

        const resets_at_str = usage.asString(raw_window.get("resetsAt"));
        const resets_at: ?i64 = if (resets_at_str) |s| iso8601.parseIso8601(s) else null;

        const raw_status = usage.asString(raw_window.get("status"));
        const is_rate_limited = if (raw_status) |rs| blk: {
            const lower = try alloc.alloc(u8, rs.len);
            _ = std.ascii.lowerString(lower, rs);
            break :blk std.mem.eql(u8, lower, "rate-limited") or std.mem.eql(u8, lower, "exhausted");
        } else false;

        const status: []const u8 = if (is_rate_limited or (used_percent != null and used_percent.? >= 100.0))
            "exhausted"
        else
            usage.statusFor(used_percent);

        try limits.append(alloc, .{
            .id = win.key,
            .label = win.label,
            .usedPercent = used_percent,
            .windowLabel = win.windowLabel,
            .windowSeconds = win.windowSeconds,
            .resetsAt = resets_at,
            .status = status,
        });
    }

    return limits.items;
}

pub fn advance(
    alloc: std.mem.Allocator,
    credential_json: []const u8,
    responses_json: []const u8,
    _now_ms: i64,
) !usage.Outcome {
    _ = _now_ms;
    const cred_parsed = std.json.parseFromSlice(std.json.Value, alloc, credential_json, .{}) catch {
        return .{ .report = .{ .error_message = "invalid credential JSON" } };
    };
    const cred_root = usage.asObject(cred_parsed.value) orelse {
        return .{ .report = .{ .error_message = "invalid credential JSON" } };
    };
    const kind = usage.asString(cred_root.get("kind")) orelse "";
    const is_oauth = std.mem.eql(u8, kind, "oauth");
    const is_api_key = std.mem.eql(u8, kind, "apiKey");
    if (!is_oauth and !is_api_key) {
        return .{ .report = .{ .error_message = "opencode-go needs an apiKey or OAuth credential" } };
    }

    const api_key = if (is_oauth)
        usage.asString(cred_root.get("access")) orelse ""
    else
        usage.asString(cred_root.get("apiKey")) orelse "";
    const cred_account_id = usage.asString(cred_root.get("accountId"));
    const cred_email = usage.asString(cred_root.get("email"));

    const resp_parsed = std.json.parseFromSlice(std.json.Value, alloc, responses_json, .{}) catch {
        return .{ .report = .{ .error_message = "invalid responses JSON" } };
    };
    const resp_array = usage.asArray(resp_parsed.value) orelse {
        return .{ .report = .{ .error_message = "invalid responses JSON" } };
    };

    if (resp_array.items.len == 0) {
        var headers = std.ArrayList([2][]const u8).empty;
        const auth_hdr = try std.fmt.allocPrint(alloc, "Bearer {s}", .{api_key});
        try headers.append(alloc, .{ "Authorization", auth_hdr });
        try headers.append(alloc, .{ "Accept", "application/json" });
        try headers.append(alloc, .{ "User-Agent", "OpenCode-Status-Plugin/1.0" });

        const requests = try alloc.alloc(usage.UsageRequest, 1);
        requests[0] = .{
            .method = "GET",
            .url = OPENCODE_GO_USAGE_URL,
            .headers = headers.items,
        };
        return .{ .requests = requests };
    }

    const first = switch (try usage.parseFirstResponse(alloc, resp_array, "usage body is not JSON")) {
        .ok => |f| f,
        .err => |msg| return .{ .report = .{ .error_message = msg } },
    };
    const body_parsed = first.parsed;

    const limits = try parseOpenCodeGoUsage(alloc, body_parsed.value);

    return .{
        .report = .{
            .planType = "Go",
            .accountId = cred_account_id,
            .email = cred_email,
            .limits = limits,
            .notes = &[_][]const u8{"OpenCode Go unified models"},
            .error_message = null,
        },
    };
}
