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, runUsageFeed, type UsageCredential, type UsageResponse } from "../src/index.js";
import {
	ALIBABA_CLI_TIMEOUT_MS,
	ALIBABA_QUOTA_CONFIG_API,
	ALIBABA_RESET_CARD_API,
	ALIBABA_SUBSCRIPTION_API,
	ALIBABA_USAGE_API,
	alibabaCliArguments,
	alibabaUsageFeed,
	parseAlibabaQuotaConfig,
	parseAlibabaUsage,
} from "../src/utils/usage/providers/alibaba.js";

const fixtures = resolve(
	dirname(fileURLToPath(import.meta.url)),
	"..",
	"..",
	"..",
	"native",
	"usage",
	"tests",
	"fixtures",
);
const fixture = (name: string): string => readFileSync(join(fixtures, name), "utf8");

const cliCredential: UsageCredential = { kind: "cli", config: { region: "intl-personal" }, email: "user@example.com" };
const sessionCredential: UsageCredential = {
	kind: "session",
	session: {
		url: "https://bailian-singapore-cs.alibabacloud.com/data/api.json",
		region: "ap-southeast-1",
		secToken: "csrf-token",
		headers: { cookie: "login_aliyunid=test" },
	},
};
const cliAnswer = (status: number, body: string): UsageResponse[] => [{ status, body }];

/** The four console responses, in the order the feed asks for them. */
const consoleResponses: UsageResponse[] = [
	{ status: 200, body: fixture("alibaba-usage.json") },
	{ status: 200, body: fixture("alibaba-subscription.json") },
	{ status: 200, body: fixture("alibaba-quota-config.json") },
	{ status: 200, body: fixture("alibaba-reset-card_list.json") },
];

describe("alibaba feed: the bailian CLI first", () => {
	it("asks the host for the official CLI, not for HTTP", () => {
		const step = alibabaUsageFeed.advance(createUsageState(cliCredential, 0), []);
		if (!("requests" in step)) throw new Error("expected requests");
		expect(step.requests).toHaveLength(1);
		const request = step.requests[0]!;
		expect(request.kind).toBe("command");
		if (request.kind !== "command") throw new Error("expected a command request");
		expect(request.command).toBe("bl");
		expect(request.args).toEqual(alibabaCliArguments("intl-personal"));
		expect(request.args).toContain("ap-southeast-1");
		expect(request.args).toContain("international");
		expect(request.timeoutMs).toBe(ALIBABA_CLI_TIMEOUT_MS);
	});

	it("honours the region from the credential", () => {
		expect(alibabaCliArguments("cn-personal")).toEqual([
			"usage",
			"token-plan",
			"--console-region",
			"cn-beijing",
			"--console-site",
			"domestic",
			"--output",
			"json",
		]);
		const step = alibabaUsageFeed.advance(
			createUsageState({ kind: "cli", config: { region: "cn-personal" } }, 0),
			[],
		);
		if (!("requests" in step) || step.requests[0]?.kind !== "command") throw new Error("expected a command request");
		expect(step.requests[0].args).toContain("domestic");
	});

	it("reads the CLI's own output: a FRACTION to percent, epoch milliseconds kept as-is", () => {
		const step = alibabaUsageFeed.advance(
			createUsageState(cliCredential, 1_700_000_000_000),
			cliAnswer(
				0,
				JSON.stringify({
					per5HourPercentage: 0.5,
					per1WeekPercentage: 0.83603712139856,
					per1WeekResetTime: 1_790_021_160_000,
				}),
			),
		);
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.limits).toHaveLength(2);
		expect(step.report.limits[0]).toMatchObject({
			label: "Session",
			usedPercent: 50,
			windowLabel: "5h window",
			status: "ok",
		});
		const weekly = step.report.limits[1]!;
		expect(weekly.label).toBe("Weekly");
		expect(weekly.usedPercent).toBeCloseTo(83.6037, 3);
		expect(weekly.windowLabel).toBe("7d window");
		// epoch MILLISECONDS already — nothing to convert, which is why the comment in the parser says so
		expect(weekly.resetsAt).toBe(1_790_021_160_000);
		expect(step.report.notes?.[0]).toContain("bailian CLI");
	});

	it("parses the console envelope with the same rule", () => {
		const wrapped = parseAlibabaUsage(JSON.parse(fixture("alibaba-usage.json")));
		expect(wrapped.limits[0]).toMatchObject({ label: "Weekly", windowLabel: "7d window" });
		expect(wrapped.limits[0]!.usedPercent).toBeCloseTo(83.6037, 3);
	});

	it("reports a failing CLI when there is no session to fall back to", () => {
		const step = alibabaUsageFeed.advance(createUsageState(cliCredential, 0), cliAnswer(1, "not signed in"));
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.error).toContain("bailian CLI failed");
		expect(step.report.notes?.[0]).toContain("session");
	});

	it("quotes a JSON error body's message and hint, not its braces", () => {
		const step = alibabaUsageFeed.advance(
			createUsageState(cliCredential, 0),
			cliAnswer(
				3,
				JSON.stringify({
					error: { code: 3, message: "No console access token found.", hint: "Run `bl auth login --console`." },
				}),
			),
		);
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.error).toContain("bailian CLI failed (exit 3)");
		expect(step.report.error).toContain("No console access token found. Run `bl auth login --console`.");
	});

	it("does not quote a JSON body that carries no message", () => {
		const step = alibabaUsageFeed.advance(createUsageState(cliCredential, 0), cliAnswer(3, '{"trace":"abc"}'));
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.error).toBe("bailian CLI failed (exit 3)");
	});
});

describe("alibaba feed: the console session fallback", () => {
	it("falls back to the console when the host runs no commands", () => {
		const step = alibabaUsageFeed.advance(createUsageState(sessionCredential, 0), cliAnswer(127, "no commands here"));
		if (!("requests" in step)) throw new Error("expected the console requests");
		const urls = step.requests.map((request) =>
			request.kind === "http" ? new URL(request.url).searchParams.get("api") : request.command,
		);
		expect(urls).toEqual([
			ALIBABA_USAGE_API,
			ALIBABA_SUBSCRIPTION_API,
			ALIBABA_QUOTA_CONFIG_API,
			ALIBABA_RESET_CARD_API,
		]);
		for (const request of step.requests) {
			if (request.kind !== "http") throw new Error("console calls are HTTP");
			expect(request.method).toBe("POST");
			expect(request.headers?.cookie).toBe("login_aliyunid=test");
			expect(request.body).toContain("sec_token=csrf-token");
			expect(request.body).toContain("region=ap-southeast-1");
		}
	});

	it("falls back to the console when the CLI fails", () => {
		const step = alibabaUsageFeed.advance(createUsageState(sessionCredential, 0), cliAnswer(1, "boom"));
		expect("requests" in step && step.requests).toHaveLength(4);
	});

	it("omits an absent security token instead of failing on it", () => {
		const withoutToken: UsageCredential = {
			kind: "session",
			session: {
				url: "https://bailian-singapore-cs.alibabacloud.com/data/api.json",
				region: "ap-southeast-1",
				headers: {},
			},
		};
		const step = alibabaUsageFeed.advance(createUsageState(withoutToken, 0), cliAnswer(127, ""));
		if (!("requests" in step)) throw new Error("expected requests");
		const first = step.requests[0]!;
		if (first.kind !== "http") throw new Error("expected http");
		expect(first.body).not.toContain("sec_token");
		expect(first.body).toContain(`params=`);
	});

	it("says how to get a session when there is neither a CLI nor one captured", () => {
		const step = alibabaUsageFeed.advance(createUsageState({ kind: "cli" }, 0), cliAnswer(127, ""));
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.error).toContain("capture-alibaba-session.mjs");
	});

	it("reports the console answers: plan, remaining days, tier credits and reset cards", () => {
		const step = alibabaUsageFeed.advance(createUsageState(sessionCredential, 0), consoleResponses);
		if (!("report" in step)) throw new Error("expected a report");
		expect(step.report.planType).toBe("pro");
		expect(step.report.limits[0]!.windowLabel).toBe("7d window");
		const notes = step.report.notes ?? [];
		expect(notes.some((note) => note.includes("days remaining"))).toBe(true);
		expect(notes.some((note) => note.includes("Token plan pro"))).toBe(true);
	});

	it("names the tier allowance from quota-config", () => {
		const notes = parseAlibabaQuotaConfig(JSON.parse(fixture("alibaba-quota-config.json")), "pro");
		expect(notes).toEqual(["Token plan pro: weekly 40000, 5h 12000 credits"]);
		expect(parseAlibabaQuotaConfig({}, "pro")).toEqual([]);
	});
});

describe("alibaba through the host loop", () => {
	it("drives the CLI path with a host that runs commands", async () => {
		const seen: string[] = [];
		const report = await runUsageFeed(alibabaUsageFeed, cliCredential, {
			nowMs: 1_700_000_000_000,
			runCommand: async (request) => {
				seen.push(`${request.command} ${request.args.join(" ")}`);
				return {
					status: 0,
					body: JSON.stringify({ per1WeekPercentage: 0.25, per1WeekResetTime: 1_790_021_160_000 }),
				};
			},
		});
		expect(seen[0]).toContain("bl usage token-plan --console-region ap-southeast-1");
		expect(report.limits[0]).toMatchObject({ label: "Weekly", usedPercent: 25 });
	});

	it("falls back through the loop when the host has no commands", async () => {
		const urls: string[] = [];
		const report = await runUsageFeed(alibabaUsageFeed, sessionCredential, {
			fetchImpl: async (input) => {
				urls.push(String(input));
				const api = new URL(String(input)).searchParams.get("api");
				const body =
					api === ALIBABA_USAGE_API
						? fixture("alibaba-usage.json")
						: api === ALIBABA_SUBSCRIPTION_API
							? fixture("alibaba-subscription.json")
							: api === ALIBABA_QUOTA_CONFIG_API
								? fixture("alibaba-quota-config.json")
								: fixture("alibaba-reset-card_list.json");
				return new Response(body, { status: 200 });
			},
		});
		expect(urls).toHaveLength(4);
		expect(report.planType).toBe("pro");
		expect(report.error).toBeNull();
	});
});
