import Foundation
import UsageCore

// Proves the Swift -> C ABI -> core path, with no network: the responses come from the recorded
// fixtures the TypeScript and Zig implementations are compared against.
// Pass the fixtures directory as argv[1], or let the two likely working directories decide: the
// package directory (`swift run` from swift/) or repository root.
func locateFixtures() -> URL {
	if CommandLine.arguments.count > 1 {
		return URL(fileURLWithPath: CommandLine.arguments[1])
	}
	let cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
	for candidate in ["../tests/fixtures", "tests/fixtures"] {
		let url = cwd.appendingPathComponent(candidate)
		if FileManager.default.fileExists(atPath: url.appendingPathComponent("codex-usage-live.json").path) {
			return url
		}
	}
	fail("cannot find tests/fixtures; pass the directory as the first argument")
}

let fixtures = locateFixtures()

func fixture(_ name: String) throws -> String {
	try String(contentsOf: fixtures.appendingPathComponent(name), encoding: .utf8)
}

func fail(_ message: String) -> Never {
	FileHandle.standardError.write(Data("FAIL: \(message)\n".utf8))
	exit(1)
}

func check(_ condition: Bool, _ message: String) {
	if !condition { fail(message) }
}

do {
	check(UsageCore.abiVersion == 4, "unexpected ABI version \(UsageCore.abiVersion)")

	let providers = try UsageCore.providers()
	check(providers.contains { $0.provider == "openai-codex" }, "codex should be a known provider")
	check(providers.first { $0.provider == "anthropic" }?.credentialKinds == ["oauth"], "anthropic takes oauth")
	check(providers.first { $0.provider == "opencode-go" }?.credentialKinds == ["oauth", "apiKey"], "opencode-go takes oauth and apiKey")

	let codexCredential = UsageDriver.credentialJSON(kind: "oauth", value: "test-token", email: "codex@example.com")
	let firstStep = try UsageCore.advance(provider: "openai-codex", credentialJSON: codexCredential, responses: [], nowMs: 1_789_514_000_000)
	guard case .requests(let requests) = firstStep else { fail("expected a request, not a report") }
	check(requests.count == 1, "codex needs one request")
	check(requests[0].url.hasSuffix("/wham/usage"), "unexpected codex url \(requests[0].url)")
	check(requests[0].headers["Authorization"] == "Bearer test-token", "codex must send the bearer token")

	guard let codexReport = try UsageCore.advance(
		provider: "openai-codex",
		credentialJSON: codexCredential,
		responses: [UsageResponse(status: 200, body: try fixture("codex-usage-live.json"))],
		nowMs: 1_789_514_000_000
	).reportValue else { fail("codex should report") }

	check(codexReport.planType == "prolite", "plan type \(codexReport.planType ?? "nil")")
	check(codexReport.limits.count == 3, "expected three windows, got \(codexReport.limits.count)")
	check(codexReport.limits.first?.status == "exhausted", "the primary window is at 100%")

	// anthropic: round 1 fetches usage and profile concurrently; round 2 reports with ISO reset converted to epoch ms.
	let anthropicCredential = UsageDriver.credentialJSON(kind: "oauth", value: "test-token")
	let anthropicStep = try UsageCore.advance(provider: "anthropic", credentialJSON: anthropicCredential, responses: [], nowMs: 1_700_000_000_000)
	guard case .requests(let anthropicRequests) = anthropicStep else { fail("anthropic should ask for requests") }
	check(anthropicRequests.count == 2, "anthropic asks for usage and profile, got \(anthropicRequests.count)")

	guard let anthropicReport = try UsageCore.advance(
		provider: "anthropic",
		credentialJSON: anthropicCredential,
		responses: [
			UsageResponse(status: 200, body: try fixture("anthropic-usage.json")),
			UsageResponse(status: 200, body: try fixture("anthropic-profile.json")),
		],
		nowMs: 1_700_000_000_000
	).reportValue else { fail("anthropic should report") }

	check(anthropicReport.limits.count == 2, "expected session and weekly, got \(anthropicReport.limits.count)")
	let weekly = anthropicReport.limits[1]
	check(weekly.label == "Weekly", "second window is the week")
	check(weekly.usedPercent == 94, "weekly is 94% used, got \(weekly.usedPercent ?? -1)")
	check(weekly.resetsAt == 1_789_545_600_037, "ISO reset should become epoch ms, got \(weekly.resetsAt ?? -1)")
	check(weekly.status == "warning", "severity critical is a warning below 100%")
	check(anthropicReport.planType == "team_standard", "plan type from the profile")


	// google-antigravity: the endpoint is gated on the antigravity user-agent, and the body reports
	// REMAINING fraction, so usedPercent is an inversion. Both are pinned here because both are easy
	// to "fix" into something that looks fine and is wrong.
	let antigravityCredential = UsageDriver.credentialJSON(kind: "oauth", value: "ya29.test")
	let antigravityStep = try UsageCore.advance(provider: "google-antigravity", credentialJSON: antigravityCredential, responses: [], nowMs: 1_789_514_000_000)
	guard case .requests(let antigravityRequests) = antigravityStep else { fail("antigravity should ask for a request") }
	check(antigravityRequests.count == 1, "antigravity needs one request")
	check(antigravityRequests[0].method == "POST", "antigravity is a POST")
	check(antigravityRequests[0].headers["User-Agent"]?.hasPrefix("antigravity/") == true, "without the antigravity user-agent the endpoint answers 403")

	guard let antigravityReport = try UsageCore.advance(
		provider: "google-antigravity",
		credentialJSON: antigravityCredential,
		responses: [UsageResponse(status: 200, body: try fixture("antigravity-quota.json"))],
		nowMs: 1_789_514_000_000
	).reportValue else { fail("antigravity should report") }

	guard let gemini = antigravityReport.limits.first(where: { $0.label == "Gemini" }) else {
		fail("antigravity should pool a Gemini family, got \(antigravityReport.limits.map(\.label))")
	}
	check(gemini.usedPercent == 83.4, "remaining 0.166 must invert to 83.4, got \(gemini.usedPercent ?? -1)")
	check(gemini.detail == "17% free", "the detail keeps the free share, got \(gemini.detail ?? "nil")")
	check(!antigravityReport.limits.contains { $0.label.lowercased().contains("chat") || $0.label.lowercased().contains("tab") }, "internal buckets must be dropped")

	// alibaba: CLI-first, 127 fallback to console session, and a FRACTION that has to become a percentage.
	let alibabaSession = #"{"url":"https://bailian-singapore-cs.alibabacloud.com/data/api.json","region":"ap-southeast-1","secToken":"test-csrf","headers":{"cookie":"login_aliyunid=test"}}"#
	let alibabaStep = try UsageCore.advance(provider: "alibaba", credentialJSON: UsageDriver.credentialJSON(kind: "session", value: alibabaSession), responses: [], nowMs: 1_789_514_000_000)
	guard case .requests(let cliRequests) = alibabaStep else { fail("alibaba should ask for the bl command first") }
	check(cliRequests.count == 1, "alibaba asks for one command request first, got \(cliRequests.count)")
	check(cliRequests[0].kind == "command", "first request is a command")
	check(cliRequests[0].command == "bl", "command is bl")
	check(cliRequests[0].args?.contains("usage") == true, "args contain usage")
	check(cliRequests[0].args?.contains("token-plan") == true, "args contain token-plan")
	check(cliRequests[0].args?.contains("--console-region") == true, "args contain --console-region")
	check(cliRequests[0].args?.contains("ap-southeast-1") == true, "args contain ap-southeast-1")
	check(cliRequests[0].args?.contains("international") == true, "args contain international")

	// Host runs no commands -> falls back to four console HTTP requests
	let noCommands = [UsageResponse(status: 127, body: "this host does not run commands (bl)")]
	let consoleStep = try UsageCore.advance(provider: "alibaba", credentialJSON: UsageDriver.credentialJSON(kind: "session", value: alibabaSession), responses: noCommands, nowMs: 1_789_514_000_000)
	guard case .requests(let alibabaRequests) = consoleStep else { fail("alibaba should ask for its four console calls on 127") }
	check(alibabaRequests.count == 4, "alibaba reads usage, subscription, quota-config and reset cards, got \(alibabaRequests.count)")
	check(alibabaRequests[0].kind == "http", "console calls are HTTP")
	check(alibabaRequests[0].method == "POST", "the console gateway is a POST")
	check(alibabaRequests[0].headers["cookie"] == "login_aliyunid=test", "the captured cookies have to travel with the request")
	check(alibabaRequests[0].body?.contains("sec_token=test-csrf") == true, "the form body carries the session csrf token")
	check(alibabaRequests[0].body?.contains("region=ap-southeast-1") == true, "and the region")

	guard let alibabaReport = try UsageCore.advance(
		provider: "alibaba",
		credentialJSON: UsageDriver.credentialJSON(kind: "session", value: alibabaSession),
		responses: [
			UsageResponse(status: 200, body: try fixture("alibaba-usage.json")),
			UsageResponse(status: 200, body: try fixture("alibaba-subscription.json")),
			UsageResponse(status: 200, body: try fixture("alibaba-quota-config.json")),
			UsageResponse(status: 200, body: try fixture("alibaba-reset-card_list.json")),
		],
		nowMs: 1_789_514_000_000
	).reportValue else { fail("alibaba should report from console") }

	check(alibabaReport.planType == "pro", "plan type is pro")
	check(alibabaReport.limits.count == 1, "expected weekly limit")
	check(alibabaReport.limits[0].label == "Weekly", "weekly label")
	check(alibabaReport.notes?.contains(where: { $0.contains("days remaining in the plan") }) == true, "notes carry remaining days")
	check(alibabaReport.notes?.contains(where: { $0.contains("Token plan pro") }) == true, "notes carry quota config")

	// hyper: multi-round flow with UsageState preserving team across calls
	let hyperState = UsageState()
	let hyperCredential = UsageDriver.credentialJSON(kind: "apiKey", value: "sk-hyper-test")
	let hyperStep0 = try UsageCore.advance(state: hyperState, provider: "hyper", credentialJSON: hyperCredential, responses: [], nowMs: 1_789_514_000_000)
	guard case .requests(let hyperReq0) = hyperStep0 else { fail("hyper should ask for teams first") }
	check(hyperReq0.count == 1, "hyper round 1 asks for teams")
	check(hyperReq0[0].url.hasSuffix("/v1/teams"), "teams url")

	let hyperTeamsResponse = [UsageResponse(status: 200, body: try fixture("hyper-teams.json"))]
	let hyperStep1 = try UsageCore.advance(state: hyperState, provider: "hyper", credentialJSON: hyperCredential, responses: hyperTeamsResponse, nowMs: 1_789_514_000_000)
	guard case .requests(let hyperReq1) = hyperStep1 else { fail("hyper round 2 should ask for billing and members") }
	check(hyperReq1.count == 2, "hyper asks for billing and members")

	let hyperDataResponses = [
		UsageResponse(status: 200, body: try fixture("hyper-billing.json")),
		UsageResponse(status: 200, body: try fixture("hyper-members.json")),
	]
	guard let hyperReport = try UsageCore.advance(state: hyperState, provider: "hyper", credentialJSON: hyperCredential, responses: hyperDataResponses, nowMs: 1_789_514_000_000).reportValue else {
		fail("hyper should report")
	}
	check(hyperReport.planType == "shred", "hyper planType shred")
	check(hyperReport.notes?.contains("Team: Example Team") == true, "hyper preserved team name in state across rounds")

	// An unknown provider is an error, not an empty report.
	do {
		_ = try UsageCore.advance(provider: "nope", credentialJSON: "{}", responses: [], nowMs: 0)
		fail("an unknown provider should throw")
	} catch UsageCoreError.providerRefused(let message) {
		check(message.contains("unknown provider"), "unexpected message: \(message)")
	}

	print("usage-smoke: abi=\(UsageCore.abiVersion) providers=\(providers.count) codex=\(codexReport.limits.count) anthropic=\(anthropicReport.limits.count) hyper=\(hyperReport.limits.count) antigravity=\(antigravityReport.limits.count) alibaba=\(alibabaRequests.count) calls OK")
} catch {
	fail("\(error)")
}
