/**
 * canonical-id-dynamic.test.mjs
 *
 * Verifies that canonicalIdFor() consults the dynamic discovery cache as a
 * fallback when CANONICAL_BY_ROUTE has no entry and the route key is not in
 * the upstream _ENTRY_BY_ROUTE catalog.
 *
 * Uses the __setDiscoveryCacheForTest() hook to inject cache data without
 * touching the filesystem, avoiding vi.mock complexity with hoisting.
 */
import { afterEach, beforeEach, describe, expect, it } from "vitest";

// Import once at describe-scope level; the hook resets state per test.
const { canonicalIdFor, __setDiscoveryCacheForTest } = await import(
	"../model-registry.js"
);

// ── tests ─────────────────────────────────────────────────────────────────────

describe("canonicalIdFor — dynamic discovery cache fallback", () => {
	afterEach(() => {
		// Restore normal file-backed behaviour between tests.
		__setDiscoveryCacheForTest(undefined);
	});

	it("resolves a model present only in the discovery cache (not in static table)", () => {
		// Inject a brand-new provider/model that does NOT appear in
		// CANONICAL_BY_ROUTE or the upstream _ENTRY_BY_ROUTE catalog.
		__setDiscoveryCacheForTest({
			version: 1,
			entries: {
				newprovider: {
					models: [{ id: "new-model-3.0", provider: "newprovider" }],
				},
			},
		});

		expect(canonicalIdFor("newprovider/new-model-3.0")).toBe("new-model-3.0");
	});

	it("static aliases still win over dynamic resolver", () => {
		// kimi-coding/kimi-for-coding is in CANONICAL_BY_ROUTE → "kimi-k2.6".
		// Even if the discovery cache has the same wire id, the static table wins.
		__setDiscoveryCacheForTest({
			version: 1,
			entries: {
				"kimi-coding": {
					models: [{ id: "kimi-for-coding", provider: "kimi-coding" }],
				},
			},
		});

		// Static alias maps kimi-for-coding → kimi-k2.6, NOT "kimi-for-coding".
		expect(canonicalIdFor("kimi-coding/kimi-for-coding")).toBe("kimi-k2.6");
	});

	it("cache miss: unknown provider/model not in cache returns null", () => {
		__setDiscoveryCacheForTest({
			version: 1,
			entries: {
				someprovider: {
					models: [{ id: "real-model", provider: "someprovider" }],
				},
			},
		});

		expect(canonicalIdFor("unknown/never-existed")).toBeNull();
	});

	it("malformed cache (null): returns null gracefully (no throw)", () => {
		// Simulates what getDiscoveryCache() returns when the file is unreadable.
		__setDiscoveryCacheForTest(null);

		expect(() => canonicalIdFor("newprovider/any-model")).not.toThrow();
		expect(canonicalIdFor("newprovider/any-model")).toBeNull();
	});

	it("cache with missing models array: returns null gracefully (no throw)", () => {
		__setDiscoveryCacheForTest({
			version: 1,
			entries: {
				brokenprovider: {
					// models key is absent — simulates partial/corrupt cache entry
				},
			},
		});

		expect(() => canonicalIdFor("brokenprovider/some-model")).not.toThrow();
		expect(canonicalIdFor("brokenprovider/some-model")).toBeNull();
	});

	it("multiple models in cache: resolves each independently", () => {
		__setDiscoveryCacheForTest({
			version: 1,
			entries: {
				newprovider: {
					models: [
						{ id: "alpha-1.0", provider: "newprovider" },
						{ id: "beta-2.0", provider: "newprovider" },
					],
				},
			},
		});

		expect(canonicalIdFor("newprovider/alpha-1.0")).toBe("alpha-1.0");
		expect(canonicalIdFor("newprovider/beta-2.0")).toBe("beta-2.0");
		expect(canonicalIdFor("newprovider/gamma-3.0")).toBeNull();
	});
});

// ── Regression: removed identity-strip aliases still resolve via dynamic cache ──
//
// 15 entries were removed from CANONICAL_BY_ROUTE (commit that pruned
// identity-strip aliases) because the provider is in the discovery cache and
// canonical == bare model id. These tests verify each removed entry still
// resolves correctly through canonicalIdFromDiscovery().

describe("canonicalIdFor — removed identity-strip aliases resolve via discovery cache", () => {
	afterEach(() => {
		__setDiscoveryCacheForTest(undefined);
	});

	// Inject a fake cache that mirrors the real discovery-cache providers.
	// Tests use __setDiscoveryCacheForTest so no filesystem access is needed.

	it("minimax/MiniMax-M2.7 resolves to MiniMax-M2.7 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				minimax: { models: [{ id: "MiniMax-M2.7" }] },
			},
		});
		expect(canonicalIdFor("minimax/MiniMax-M2.7")).toBe("MiniMax-M2.7");
	});

	it("minimax/MiniMax-M2.7-highspeed resolves to MiniMax-M2.7-highspeed via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				minimax: { models: [{ id: "MiniMax-M2.7-highspeed" }] },
			},
		});
		expect(canonicalIdFor("minimax/MiniMax-M2.7-highspeed")).toBe(
			"MiniMax-M2.7-highspeed",
		);
	});

	it("mistral/codestral-latest resolves to codestral-latest via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "codestral-latest" }] },
			},
		});
		expect(canonicalIdFor("mistral/codestral-latest")).toBe("codestral-latest");
	});

	it("mistral/devstral-2512 resolves to devstral-2512 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "devstral-2512" }] },
			},
		});
		expect(canonicalIdFor("mistral/devstral-2512")).toBe("devstral-2512");
	});

	it("mistral/devstral-small-2507 resolves to devstral-small-2507 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "devstral-small-2507" }] },
			},
		});
		expect(canonicalIdFor("mistral/devstral-small-2507")).toBe(
			"devstral-small-2507",
		);
	});

	it("mistral/mistral-large-latest resolves to mistral-large-latest via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "mistral-large-latest" }] },
			},
		});
		expect(canonicalIdFor("mistral/mistral-large-latest")).toBe(
			"mistral-large-latest",
		);
	});

	it("mistral/mistral-medium-latest resolves to mistral-medium-latest via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "mistral-medium-latest" }] },
			},
		});
		expect(canonicalIdFor("mistral/mistral-medium-latest")).toBe(
			"mistral-medium-latest",
		);
	});

	it("mistral/mistral-small-latest resolves to mistral-small-latest via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				mistral: { models: [{ id: "mistral-small-latest" }] },
			},
		});
		expect(canonicalIdFor("mistral/mistral-small-latest")).toBe(
			"mistral-small-latest",
		);
	});

	it("zai/glm-4.5 resolves to glm-4.5 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-4.5" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-4.5")).toBe("glm-4.5");
	});

	it("zai/glm-4.5-air resolves to glm-4.5-air via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-4.5-air" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-4.5-air")).toBe("glm-4.5-air");
	});

	it("zai/glm-4.6 resolves to glm-4.6 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-4.6" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-4.6")).toBe("glm-4.6");
	});

	it("zai/glm-4.7 resolves to glm-4.7 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-4.7" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-4.7")).toBe("glm-4.7");
	});

	it("zai/glm-5 resolves to glm-5 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-5" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-5")).toBe("glm-5");
	});

	it("zai/glm-5-turbo resolves to glm-5-turbo via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-5-turbo" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-5-turbo")).toBe("glm-5-turbo");
	});

	it("zai/glm-5.1 resolves to glm-5.1 via discovery cache", () => {
		__setDiscoveryCacheForTest({
			entries: {
				zai: { models: [{ id: "glm-5.1" }] },
			},
		});
		expect(canonicalIdFor("zai/glm-5.1")).toBe("glm-5.1");
	});
});
