//! OpenAI Codex usage feed implementation.
//!
//! Mirrors prime-agent:packages/ai/src/utils/usage/providers/codex.ts field for field.
const std = @import("std");
const usage = @import("../usage.zig");

pub const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";

fn parseWindow(alloc: std.mem.Allocator, id: []const u8, window_val: ?std.json.Value, now_ms: i64, scope: ?[]const u8) !?usage.UsageLimit {
    const value = usage.asObject(window_val) orelse return null;
    const used_percent = usage.asNumber(value.get("used_percent"));
    const reset_at = usage.asNumber(value.get("reset_at"));
    const reset_after = usage.asNumber(value.get("reset_after_seconds"));
    const limit_window = usage.asNumber(value.get("limit_window_seconds"));

    const reset_ms: ?i64 = if (reset_at) |ra|
        (if (ra > 1_000_000_000_000.0) usage.toInt(i64, ra) else usage.toInt(i64, ra * 1000.0))
    else if (reset_after) |after| blk: {
        const ms = usage.toInt(i64, after * 1000.0) orelse break :blk null;
        break :blk now_ms + ms;
    } else
        null;

    const window_lbl = try usage.formatWindowLabel(alloc, limit_window);
    const window_seconds: ?i64 = if (limit_window) |lw| (if (lw > 0) usage.roundToInt(i64, lw) else null) else null;

    return usage.UsageLimit{
        .id = id,
        .label = "Usage",
        .usedPercent = used_percent,
        .windowLabel = window_lbl,
        .windowSeconds = window_seconds,
        .resetsAt = reset_ms,
        .scope = scope,
        .status = usage.statusFor(used_percent),
    };
}

pub const ParseResult = struct {
    planType: ?[]const u8 = null,
    limits: []const usage.UsageLimit = &[_]usage.UsageLimit{},
};

pub fn parseCodexUsage(alloc: std.mem.Allocator, payload: ?std.json.Value, now_ms: i64) !ParseResult {
    const root = usage.asObject(payload);
    const rate_limit = if (root) |r| usage.asObject(r.get("rate_limit")) else null;
    var limits = std.ArrayList(usage.UsageLimit).empty;

    const primary_val = if (rate_limit) |rl| rl.get("primary_window") else null;
    if (try parseWindow(alloc, "primary", primary_val, now_ms, null)) |pw| {
        var w = pw;
        w.label = "Primary";
        try limits.append(alloc, w);
    }

    const secondary_val = if (rate_limit) |rl| rl.get("secondary_window") else null;
    if (try parseWindow(alloc, "secondary", secondary_val, now_ms, null)) |sw| {
        var w = sw;
        w.label = "Secondary";
        try limits.append(alloc, w);
    }

    const additional = if (root) |r| usage.asArray(r.get("additional_rate_limits")) else null;
    if (additional) |arr| {
        for (arr.items) |entry| {
            const item = usage.asObject(entry) orelse continue;
            const extra = usage.asObject(item.get("rate_limit"));
            const limit_name = usage.asString(item.get("limit_name"));
            const feature = usage.asString(item.get("metered_feature"));
            const name = limit_name orelse feature orelse "Additional";
            // The metered feature is the machine key; the limit name is what it restricts.
            const key = feature orelse limit_name orelse "additional";
            const scope = limit_name orelse feature;

            const extra_primary = if (extra) |e| e.get("primary_window") else null;
            if (try parseWindow(alloc, try std.fmt.allocPrint(alloc, "{s}.primary", .{key}), extra_primary, now_ms, scope)) |ep| {
                var w = ep;
                w.label = try std.fmt.allocPrint(alloc, "{s} · Primary", .{name});
                try limits.append(alloc, w);
            }

            const extra_secondary = if (extra) |e| e.get("secondary_window") else null;
            if (try parseWindow(alloc, try std.fmt.allocPrint(alloc, "{s}.secondary", .{key}), extra_secondary, now_ms, scope)) |es| {
                var w = es;
                w.label = try std.fmt.allocPrint(alloc, "{s} · Secondary", .{name});
                try limits.append(alloc, w);
            }
        }
    }

    const plan_type = if (root) |r| usage.asString(r.get("plan_type")) else null;
    return ParseResult{
        .planType = plan_type,
        .limits = limits.items,
    };
}

fn jwtClaim(alloc: std.mem.Allocator, token: []const u8, namespace: []const u8, key: []const u8) ?[]const u8 {
    const payload = usage.jwtPayload(alloc, token) catch return null;
    const parsed = std.json.parseFromSlice(std.json.Value, alloc, payload, .{}) catch return null;
    const root = usage.asObject(parsed.value) orelse return null;
    const ns = usage.asObject(root.get(namespace)) orelse return null;
    return usage.asString(ns.get(key));
}

fn jwtClaimEmail(alloc: std.mem.Allocator, token: []const u8) ?[]const u8 {
    const email_raw = jwtClaim(alloc, token, "https://api.openai.com/profile", "email") orelse return null;
    const trimmed = std.mem.trim(u8, email_raw, " \t\r\n");
    if (trimmed.len == 0) return null;
    const lowered = alloc.alloc(u8, trimmed.len) catch return null;
    return std.ascii.lowerString(lowered, trimmed);
}

pub fn advance(
    alloc: std.mem.Allocator,
    credential_json: []const u8,
    responses_json: []const u8,
    now_ms: i64,
) !usage.Outcome {
    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 "";
    if (!std.mem.eql(u8, kind, "oauth")) {
        return .{ .report = .{ .error_message = "openai-codex needs an OAuth account" } };
    }

    const access = usage.asString(cred_root.get("access")) orelse "";
    const cred_account_id = usage.asString(cred_root.get("accountId"));
    const account_id = if (cred_account_id != null and cred_account_id.?.len > 0)
        cred_account_id
    else
        jwtClaim(alloc, access, "https://api.openai.com/auth", "chatgpt_account_id");

    const cred_email = usage.asString(cred_root.get("email"));
    const email = if (cred_email != null and cred_email.?.len > 0)
        cred_email
    else
        jwtClaimEmail(alloc, access);

    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}", .{access});
        try headers.append(alloc, .{ "Authorization", auth_hdr });
        try headers.append(alloc, .{ "User-Agent", "prime-agent" });
        if (account_id) |aid| {
            if (aid.len > 0) {
                try headers.append(alloc, .{ "ChatGPT-Account-Id", aid });
            }
        }
        const requests = try alloc.alloc(usage.UsageRequest, 1);
        requests[0] = .{
            .method = "GET",
            .url = CODEX_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 parsed_usage = try parseCodexUsage(alloc, first.parsed.value, now_ms);

    return .{
        .report = .{
            .planType = parsed_usage.planType,
            .limits = parsed_usage.limits,
            .accountId = account_id,
            .email = email,
            .error_message = null,
        },
    };
}
