import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import { createUsageState, type FetchLike, httpRequest, runUsageFeed, type UsageCredential } from "../src/index.js";
import { DEEPSEEK_USAGE_URL, deepseekUsageFeed, parseDeepSeekUsage } from "../src/utils/usage/providers/deepseek.js";
import {
	OPENCODE_GO_USAGE_URL,
	opencodegoUsageFeed,
	parseOpenCodeGoUsage,
} from "../src/utils/usage/providers/opencode-go.js";
import { parseZaiUsage, ZAI_USAGE_URL, zaiUsageFeed } from "../src/utils/usage/providers/zai.js";

const fixturesDir = resolve(
	dirname(fileURLToPath(import.meta.url)),
	"..",
	"..",
	"..",
	"native",
	"usage",
	"tests",
	"fixtures",
);

const fixture = (name: string): unknown => JSON.parse(readFileSync(join(fixturesDir, name), "utf8"));

function scriptedFetch(responses: { status: number; body: string }[]): FetchLike {
	const queue = [...responses];
	return async () => {
		const next = queue.shift();
		if (!next) throw new Error("scripted fetch ran out of responses");
		return new Response(next.body, { status: next.status });
	};
}

describe("opencode-go usage feed", () => {
	const credential: UsageCredential = { kind: "apiKey", apiKey: "test-opencode-key" };

	it("issues a GET request with Bearer authorization and User-Agent", () => {
		const state = createUsageState(credential, 0);
		const step = opencodegoUsageFeed.advance(state, []);
		if (!("requests" in step)) throw new Error("expected requests");
		expect(step.requests).toHaveLength(1);
		const req = httpRequest(step.requests[0]!);
		expect(req.method).toBe("GET");
		expect(req.url).toBe(OPENCODE_GO_USAGE_URL);
		expect(req.headers?.Authorization).toBe("Bearer test-opencode-key");
		expect(req.headers?.Accept).toBe("application/json");
		expect(req.headers?.["User-Agent"]).toBe("OpenCode-Status-Plugin/1.0");
	});

	it("parses the recorded live fixture across 5h, 7d, and 30d windows", () => {
		const body = fixture("opencode-go-usage.json");
		const state = createUsageState(credential, 0);
		const step = opencodegoUsageFeed.advance(state, [{ status: 200, body: JSON.stringify(body) }]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.planType).toBe("Go");
		expect(step.report.error).toBeNull();
		expect(step.report.notes).toEqual(["OpenCode Go unified models"]);
		expect(step.report.limits).toHaveLength(3);

		const [session, weekly, monthly] = step.report.limits;
		expect(session).toEqual({
			label: "Session",
			windowLabel: "5h window",
			usedPercent: 0,
			resetsAt: Date.parse("2026-09-16T04:40:14.037Z"),
			status: "ok",
		});
		expect(weekly).toEqual({
			label: "Weekly",
			windowLabel: "7d window",
			usedPercent: 0,
			resetsAt: Date.parse("2026-09-21T00:00:00.037Z"),
			status: "ok",
		});
		expect(monthly).toEqual({
			label: "Monthly",
			windowLabel: "30d window",
			usedPercent: 100,
			resetsAt: Date.parse("2026-09-29T07:01:56.037Z"),
			status: "exhausted",
		});
	});

	it("treats rate-limited status as exhausted even under 100 percent", () => {
		const parsed = parseOpenCodeGoUsage({
			usage: {
				rolling: { status: "rate-limited", percent: 42, resetsAt: "2026-09-16T04:40:14.037Z" },
			},
		});
		expect(parsed.limits).toHaveLength(1);
		expect(parsed.limits[0]!.status).toBe("exhausted");
	});

	it("maps warnings at or above 90 percent", () => {
		const parsed = parseOpenCodeGoUsage({
			usage: {
				rolling: { status: "ok", percent: 92, resetsAt: "2026-09-16T04:40:14.037Z" },
			},
		});
		expect(parsed.limits[0]!.status).toBe("warning");
	});

	it("rejects non-apiKey credential kinds", () => {
		const oauthCred: UsageCredential = { kind: "oauth", access: "token" };
		const step = opencodegoUsageFeed.advance(createUsageState(oauthCred, 0), []);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.error).toContain("apiKey");
	});

	it("handles non-200 responses as error reports", () => {
		const state = createUsageState(credential, 0);
		const step = opencodegoUsageFeed.advance(state, [{ status: 401, body: "Unauthorized" }]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.error).toBe("HTTP 401");
	});

	it("runs successfully through runUsageFeed with scriptedFetch", async () => {
		const report = await runUsageFeed(opencodegoUsageFeed, credential, {
			fetchImpl: scriptedFetch([{ status: 200, body: JSON.stringify(fixture("opencode-go-usage.json")) }]),
		});
		expect(report.provider).toBe("opencode-go");
		expect(report.providerName).toBe("OpenCode Zen");
		expect(report.error).toBeNull();
		expect(report.limits).toHaveLength(3);
	});
});

describe("zai usage feed", () => {
	const credential: UsageCredential = { kind: "apiKey", apiKey: "test-zai-key" };

	it("issues a GET request with raw Authorization and User-Agent", () => {
		const state = createUsageState(credential, 0);
		const step = zaiUsageFeed.advance(state, []);
		if (!("requests" in step)) throw new Error("expected requests");
		expect(step.requests).toHaveLength(1);
		const req = httpRequest(step.requests[0]!);
		expect(req.method).toBe("GET");
		expect(req.url).toBe(ZAI_USAGE_URL);
		expect(req.headers?.Authorization).toBe("test-zai-key");
		expect(req.headers?.["User-Agent"]).toBe("OpenCode-Status-Plugin/1.0");
	});

	it("parses the recorded live fixture for requests and token quota", () => {
		const body = fixture("zai-usage.json");
		const state = createUsageState(credential, 0);
		const step = zaiUsageFeed.advance(state, [{ status: 200, body: JSON.stringify(body) }]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.planType).toBe("Lite");
		expect(step.report.error).toBeNull();
		expect(step.report.limits).toHaveLength(2);

		const [requests, tokens] = step.report.limits;
		expect(requests).toEqual({
			label: "Requests",
			usedPercent: 0,
			resetsAt: 1792027143998,
			detail: "0 / 100 reqs",
			status: "ok",
		});
		expect(tokens).toEqual({
			label: "Tokens",
			windowLabel: "5h window",
			usedPercent: 0,
			status: "ok",
		});
	});

	it("computes usedPercent from currentValue and usage/number when percentage is missing", () => {
		const parsed = parseZaiUsage({
			data: {
				limits: [
					{
						type: "TIME_LIMIT",
						unit: 5,
						currentValue: 25,
						usage: 100,
					},
				],
			},
		});
		expect(parsed.limits[0]!.usedPercent).toBe(25);
		expect(parsed.limits[0]!.detail).toBe("25 / 100 reqs");
		expect(parsed.limits[0]!.status).toBe("ok");
	});

	it("converts epoch seconds (< 1e11) to epoch milliseconds", () => {
		const parsed = parseZaiUsage({
			data: {
				limits: [
					{
						type: "TIME_LIMIT",
						unit: 5,
						percentage: 50,
						nextResetTime: 1789517000,
					},
				],
			},
		});
		expect(parsed.limits[0]!.resetsAt).toBe(1789517000 * 1000);
	});

	it("marks status as exhausted when usedPercent >= 100", () => {
		const parsed = parseZaiUsage({
			data: {
				limits: [
					{
						type: "TOKENS_LIMIT",
						unit: 3,
						percentage: 100,
					},
				],
			},
		});
		expect(parsed.limits[0]!.status).toBe("exhausted");
	});

	it("surfaces API-level errors as error reports", () => {
		const state = createUsageState(credential, 0);
		const step = zaiUsageFeed.advance(state, [
			{ status: 200, body: JSON.stringify({ code: 401, msg: "Invalid token", success: false }) },
		]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.error).toBe("Invalid token");
	});

	it("runs successfully through runUsageFeed with scriptedFetch", async () => {
		const report = await runUsageFeed(zaiUsageFeed, credential, {
			fetchImpl: scriptedFetch([{ status: 200, body: JSON.stringify(fixture("zai-usage.json")) }]),
		});
		expect(report.provider).toBe("zai");
		expect(report.providerName).toBe("Z.ai");
		expect(report.error).toBeNull();
		expect(report.limits).toHaveLength(2);
	});
});

describe("deepseek usage feed", () => {
	const credential: UsageCredential = { kind: "apiKey", apiKey: "test-deepseek-key" };

	it("issues a GET request with Bearer authorization", () => {
		const state = createUsageState(credential, 0);
		const step = deepseekUsageFeed.advance(state, []);
		if (!("requests" in step)) throw new Error("expected requests");
		expect(step.requests).toHaveLength(1);
		const req = httpRequest(step.requests[0]!);
		expect(req.method).toBe("GET");
		expect(req.url).toBe(DEEPSEEK_USAGE_URL);
		expect(req.headers?.Authorization).toBe("Bearer test-deepseek-key");
		expect(req.headers?.Accept).toBe("application/json");
	});

	it("parses the recorded live fixture as a balance-only report", () => {
		const body = fixture("deepseek-balance.json");
		const state = createUsageState(credential, 0);
		const step = deepseekUsageFeed.advance(state, [{ status: 200, body: JSON.stringify(body) }]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.planType).toBe("Pay-as-you-go");
		expect(step.report.limits).toEqual([]);
		expect(step.report.balance).toBe("$0.03 USD");
		expect(step.report.error).toBeNull();
	});

	it("formats CNY balances with currency symbol", () => {
		const parsed = parseDeepSeekUsage({
			is_available: true,
			balance_infos: [{ currency: "CNY", total_balance: "15.50" }],
		});
		expect(parsed.balance).toBe("¥15.50 CNY");
		expect(parsed.limits).toEqual([]);
	});

	it("notes when the account is unavailable", () => {
		const parsed = parseDeepSeekUsage({
			is_available: false,
			balance_infos: [{ currency: "USD", total_balance: "0.00" }],
		});
		expect(parsed.notes).toEqual(["Account is unavailable"]);
	});

	it("reports an error when balance_infos is empty", () => {
		const state = createUsageState(credential, 0);
		const step = deepseekUsageFeed.advance(state, [
			{ status: 200, body: JSON.stringify({ is_available: true, balance_infos: [] }) },
		]);
		if (!("report" in step)) throw new Error("expected report");
		expect(step.report.error).toBe("response missing balance_infos");
	});

	it("runs successfully through runUsageFeed with scriptedFetch", async () => {
		const report = await runUsageFeed(deepseekUsageFeed, credential, {
			fetchImpl: scriptedFetch([{ status: 200, body: JSON.stringify(fixture("deepseek-balance.json")) }]),
		});
		expect(report.provider).toBe("deepseek");
		expect(report.providerName).toBe("DeepSeek");
		expect(report.error).toBeNull();
		expect(report.balance).toBe("$0.03 USD");
		expect(report.limits).toEqual([]);
	});
});
