import assert from "node:assert/strict";
import { describe, it } from "vitest";
import {
	DISCOVERY_TTLS,
	getDefaultTTL,
	getDiscoverableCatalogSources,
	getDiscoverableProviders,
	getDiscoveryAdapter,
} from "./model-discovery.js";

// ─── getDiscoveryAdapter ─────────────────────────────────────────────────────

describe("getDiscoveryAdapter", () => {
	it("returns an adapter for openai", () => {
		const adapter = getDiscoveryAdapter("openai");
		assert.equal(adapter.provider, "openai");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("returns an adapter for ollama", () => {
		const adapter = getDiscoveryAdapter("ollama");
		assert.equal(adapter.provider, "ollama");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("returns an adapter for ollama-cloud", () => {
		const adapter = getDiscoveryAdapter("ollama-cloud");
		assert.equal(adapter.provider, "ollama-cloud");
		assert.equal(adapter.supportsDiscovery, true);
		assert.equal(adapter.requiresAuthForDiscovery, false);
	});

	it("returns an adapter for openrouter", () => {
		const adapter = getDiscoveryAdapter("openrouter");
		assert.equal(adapter.provider, "openrouter");
		assert.equal(adapter.supportsDiscovery, true);
		assert.equal(adapter.requiresAuthForDiscovery, false);
	});

	it("returns adapters for direct live-listed providers", () => {
		for (const provider of ["zai", "minimax", "mistral"]) {
			const adapter = getDiscoveryAdapter(provider);
			assert.equal(adapter.provider, provider);
			assert.equal(adapter.supportsDiscovery, true);
			assert.notEqual(adapter.sourceType, "catalog");
		}
	});

	it("returns an adapter for singularity-memory", () => {
		const adapter = getDiscoveryAdapter("singularity-memory");
		assert.equal(adapter.provider, "singularity-memory");
		assert.equal(adapter.supportsDiscovery, true);
		assert.equal(adapter.sourceType, "catalog");
		assert.equal(adapter.requiresAuthForDiscovery, false);
	});

	it("returns an adapter for google", () => {
		const adapter = getDiscoveryAdapter("google");
		assert.equal(adapter.provider, "google");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("returns a static adapter for anthropic", () => {
		const adapter = getDiscoveryAdapter("anthropic");
		assert.equal(adapter.provider, "anthropic");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("returns a static adapter for bedrock", () => {
		const adapter = getDiscoveryAdapter("bedrock");
		assert.equal(adapter.provider, "bedrock");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("returns a static adapter for unknown providers", () => {
		const adapter = getDiscoveryAdapter("unknown-provider");
		assert.equal(adapter.provider, "unknown-provider");
		assert.equal(adapter.supportsDiscovery, false);
	});

	it("static adapter fetchModels returns empty array", async () => {
		const adapter = getDiscoveryAdapter("anthropic");
		const models = await adapter.fetchModels("key");
		assert.deepEqual(models, []);
	});
});

// ─── getDiscoverableProviders ────────────────────────────────────────────────

describe("getDiscoverableProviders", () => {
	it("returns only providers that support discovery", () => {
		const providers = getDiscoverableProviders();
		assert.deepEqual(providers, [
			"ollama-cloud",
			"openrouter",
			"zai",
			"minimax",
			"xiaomi",
			"mistral",
		]);
		assert.ok(!providers.includes("ollama"));
		assert.ok(!providers.includes("openai"));
		assert.ok(!providers.includes("singularity-memory"));
		assert.ok(!providers.includes("google"));
		assert.ok(!providers.includes("anthropic"));
		assert.ok(!providers.includes("bedrock"));
	});

	it("returns an array of strings", () => {
		const providers = getDiscoverableProviders();
		assert.ok(Array.isArray(providers));
		for (const p of providers) {
			assert.equal(typeof p, "string");
		}
	});
});

// ─── getDiscoverableCatalogSources ───────────────────────────────────────────

describe("getDiscoverableCatalogSources", () => {
	it("returns memory catalog sources, not AI providers", () => {
		assert.deepEqual(getDiscoverableCatalogSources(), ["singularity-memory"]);
	});
});

// ─── getDefaultTTL ───────────────────────────────────────────────────────────

describe("getDefaultTTL", () => {
	it("returns default TTL for local ollama", () => {
		assert.equal(getDefaultTTL("ollama"), 24 * 60 * 60 * 1000);
	});

	it("returns default TTL for openai", () => {
		assert.equal(getDefaultTTL("openai"), 24 * 60 * 60 * 1000);
	});

	it("returns 1 hour for ollama-cloud", () => {
		assert.equal(getDefaultTTL("ollama-cloud"), 60 * 60 * 1000);
	});

	it("returns default TTL for google", () => {
		assert.equal(getDefaultTTL("google"), 24 * 60 * 60 * 1000);
	});

	it("returns 1 hour for openrouter", () => {
		assert.equal(getDefaultTTL("openrouter"), 60 * 60 * 1000);
	});

	it("returns 1 hour for direct live-listed providers", () => {
		for (const provider of [
			"zai",
			"minimax",
			"kimi-coding",
			"xiaomi",
			"mistral",
		]) {
			assert.equal(getDefaultTTL(provider), 60 * 60 * 1000);
		}
	});

	it("returns 24 hours for unknown providers", () => {
		assert.equal(getDefaultTTL("some-custom"), 24 * 60 * 60 * 1000);
	});
});

// ─── DISCOVERY_TTLS ──────────────────────────────────────────────────────────

describe("DISCOVERY_TTLS", () => {
	it("has expected keys", () => {
		assert.ok(!("ollama" in DISCOVERY_TTLS));
		assert.ok("ollama-cloud" in DISCOVERY_TTLS);
		assert.ok(!("openai" in DISCOVERY_TTLS));
		assert.ok(!("google" in DISCOVERY_TTLS));
		assert.ok("openrouter" in DISCOVERY_TTLS);
		assert.ok("zai" in DISCOVERY_TTLS);
		assert.ok("minimax" in DISCOVERY_TTLS);
		assert.ok("kimi-coding" in DISCOVERY_TTLS);
		assert.ok("xiaomi" in DISCOVERY_TTLS);
		assert.ok("mistral" in DISCOVERY_TTLS);
		assert.ok("singularity-memory" in DISCOVERY_TTLS);
		assert.ok("default" in DISCOVERY_TTLS);
	});

	it("all values are positive numbers", () => {
		for (const [, value] of Object.entries(DISCOVERY_TTLS)) {
			assert.equal(typeof value, "number");
			assert.ok(value > 0);
		}
	});
});

// ─── Singularity Memory Adapter ──────────────────────────────────────────────

describe("singularity-memory discovery", () => {
	it("uses the OpenRouter-style /v1/models endpoint and preserves source provider metadata", async () => {
		const originalFetch = globalThis.fetch;
		const calls: Array<{ url: string; headers?: RequestInit["headers"] }> = [];
		globalThis.fetch = (async (
			input: string | URL | Request,
			init?: RequestInit,
		) => {
			calls.push({ url: String(input), headers: init?.headers });
			return new Response(
				JSON.stringify({
					data: [
						{
							id: "xiaomi/mimo-v2.5-pro",
							name: "Xiaomi MiMo V2.5 Pro",
							canonical_slug: "xiaomi/mimo-v2.5-pro",
							context_length: 1048576,
							pricing: { prompt: "0.0000004", completion: "0.000002" },
							top_provider: { max_completion_tokens: 131072 },
							architecture: {
								input_modalities: ["text", "image"],
								output_modalities: ["text"],
							},
							x_singularity: {
								provider: "xiaomi",
								subprovider: "xiaomi-token-plan-ams",
								api: "anthropic-messages",
								base_url: "https://token-plan-ams.xiaomimimo.com/anthropic",
								reasoning: true,
							},
						},
					],
				}),
				{ status: 200 },
			);
		}) as typeof fetch;

		try {
			const adapter = getDiscoveryAdapter("singularity-memory");
			const models = await adapter.fetchModels("", "http://memory.local");

			assert.equal(calls[0]?.url, "http://memory.local/v1/models");
			assert.equal(calls[0]?.headers, undefined);
			assert.deepEqual(models, [
				{
					id: "mimo-v2.5-pro",
					name: "Xiaomi MiMo V2.5 Pro",
					provider: "xiaomi",
					api: "anthropic-messages",
					baseUrl: "https://token-plan-ams.xiaomimimo.com/anthropic",
					contextWindow: 1048576,
					maxTokens: 131072,
					reasoning: true,
					input: ["text", "image"],
					cost: {
						input: 0.4,
						output: 2,
						cacheRead: 0,
						cacheWrite: 0,
					},
				},
			]);
		} finally {
			globalThis.fetch = originalFetch;
		}
	});
});

// ─── Ollama Cloud Adapter ───────────────────────────────────────────────────

describe("ollama-cloud discovery", () => {
	it("uses the live Ollama /v1/models endpoint", async () => {
		const originalFetch = globalThis.fetch;
		const calls: Array<{ url: string; headers?: RequestInit["headers"] }> = [];
		globalThis.fetch = (async (
			input: string | URL | Request,
			init?: RequestInit,
		) => {
			calls.push({ url: String(input), headers: init?.headers });
			return new Response(
				JSON.stringify({
					data: [{ id: "kimi-k2.5" }, { id: "kimi-k2.6" }],
				}),
				{ status: 200 },
			);
		}) as typeof fetch;

		try {
			const adapter = getDiscoveryAdapter("ollama-cloud");
			const models = await adapter.fetchModels("test-key");

			assert.equal(calls[0]?.url, "https://ollama.com/v1/models");
			assert.deepEqual(calls[0]?.headers, {
				Authorization: "Bearer test-key",
			});
			assert.deepEqual(
				models.map((m) => m.id),
				["kimi-k2.5", "kimi-k2.6"],
			);
			assert.ok(models.every((m) => m.api === "openai-completions"));
			assert.ok(models.every((m) => m.baseUrl === "https://ollama.com/v1"));
		} finally {
			globalThis.fetch = originalFetch;
		}
	});

	it("falls back to /api/tags when /v1/models is unavailable", async () => {
		const originalFetch = globalThis.fetch;
		const calls: string[] = [];
		globalThis.fetch = (async (input: string | URL | Request) => {
			calls.push(String(input));
			if (calls.length === 1) {
				return new Response("not found", { status: 404 });
			}
			return new Response(
				JSON.stringify({
					models: [{ name: "glm-5.1" }, { model: "qwen3-coder:480b" }],
				}),
				{ status: 200 },
			);
		}) as typeof fetch;

		try {
			const adapter = getDiscoveryAdapter("ollama-cloud");
			const models = await adapter.fetchModels("test-key");

			assert.deepEqual(calls, [
				"https://ollama.com/v1/models",
				"https://ollama.com/api/tags",
			]);
			assert.deepEqual(
				models.map((m) => m.id),
				["glm-5.1", "qwen3-coder:480b"],
			);
		} finally {
			globalThis.fetch = originalFetch;
		}
	});
});

// ─── Xiaomi Adapter ─────────────────────────────────────────────────────────

describe("xiaomi discovery", () => {
	it("uses the live Xiaomi token-plan /v1/models endpoint and keeps the Anthropic execution endpoint", async () => {
		const originalFetch = globalThis.fetch;
		const calls: Array<{ url: string; headers?: RequestInit["headers"] }> = [];
		globalThis.fetch = (async (
			input: string | URL | Request,
			init?: RequestInit,
		) => {
			calls.push({ url: String(input), headers: init?.headers });
			return new Response(
				JSON.stringify({
					data: [
						{
							id: "mimo-v2.5",
							name: "MiMo V2.5",
							context_length: 1048576,
							max_output_tokens: 131072,
						},
						{
							id: "mimo-v2.5-pro",
							capabilities: { reasoning: true, vision: true },
						},
					],
				}),
				{ status: 200 },
			);
		}) as typeof fetch;

		try {
			const adapter = getDiscoveryAdapter("xiaomi");
			const models = await adapter.fetchModels("test-key");

			assert.equal(
				calls[0]?.url,
				"https://token-plan-ams.xiaomimimo.com/v1/models",
			);
			assert.deepEqual(calls[0]?.headers, {
				Authorization: "Bearer test-key",
			});
			assert.deepEqual(
				models.map((m) => m.id),
				["mimo-v2.5", "mimo-v2.5-pro"],
			);
			assert.ok(models.every((m) => m.provider === "xiaomi"));
			assert.ok(models.every((m) => m.api === "anthropic-messages"));
			assert.ok(
				models.every(
					(m) =>
						m.baseUrl === "https://token-plan-ams.xiaomimimo.com/anthropic",
				),
			);
			assert.equal(models[1]?.reasoning, true);
			assert.deepEqual(models[1]?.input, ["text", "image"]);
		} finally {
			globalThis.fetch = originalFetch;
		}
	});
});

// ─── OpenRouter Adapter ─────────────────────────────────────────────────────

describe("openrouter discovery", () => {
	it("lists only free models from the OpenRouter model endpoint", async () => {
		const originalFetch = globalThis.fetch;
		const calls: Array<{ url: string; headers?: RequestInit["headers"] }> = [];
		globalThis.fetch = (async (
			input: string | URL | Request,
			init?: RequestInit,
		) => {
			calls.push({ url: String(input), headers: init?.headers });
			return new Response(
				JSON.stringify({
					data: [
						{
							id: "qwen/qwen3-4b:free",
							name: "Qwen 3 4B Free",
							context_length: 32768,
							pricing: { prompt: "0", completion: "0" },
						},
						{
							id: "z-ai/glm-5.1",
							name: "GLM 5.1",
							context_length: 200000,
							pricing: { prompt: "0.000001", completion: "0.000004" },
						},
					],
				}),
				{ status: 200 },
			);
		}) as typeof fetch;

		try {
			const adapter = getDiscoveryAdapter("openrouter");
			const models = await adapter.fetchModels("");

			assert.equal(calls[0]?.url, "https://openrouter.ai/api/v1/models");
			assert.equal(calls[0]?.headers, undefined);
			assert.deepEqual(
				models.map((m) => m.id),
				["qwen/qwen3-4b:free"],
			);
		} finally {
			globalThis.fetch = originalFetch;
		}
	});
});

// ─── Direct Provider /models Adapters ───────────────────────────────────────

describe("direct provider discovery", () => {
	it("uses provider model-list endpoints and preserves execution metadata", async () => {
		const cases = [
			{
				provider: "zai",
				url: "https://api.z.ai/api/coding/paas/v4/models",
				model: "glm-5.1",
				api: "openai-completions",
				baseUrl: "https://api.z.ai/api/coding/paas/v4",
			},
			{
				provider: "minimax",
				url: "https://api.minimax.io/anthropic/v1/models",
				model: "MiniMax-M2.7",
				api: "anthropic-messages",
				baseUrl: "https://api.minimax.io/anthropic",
			},
			{
				provider: "mistral",
				url: "https://api.mistral.ai/v1/models",
				model: "devstral-2512",
				api: "mistral-conversations",
				baseUrl: "https://api.mistral.ai",
			},
		];

		for (const testCase of cases) {
			const originalFetch = globalThis.fetch;
			const calls: Array<{ url: string; headers?: RequestInit["headers"] }> =
				[];
			globalThis.fetch = (async (
				input: string | URL | Request,
				init?: RequestInit,
			) => {
				calls.push({ url: String(input), headers: init?.headers });
				return new Response(
					JSON.stringify({
						data: [
							{
								id: testCase.model,
								display_name: `${testCase.provider} model`,
								context_length: 262144,
								max_output_tokens: 32768,
								architecture: { input_modalities: ["text", "image"] },
								supported_parameters: ["reasoning"],
							},
						],
					}),
					{ status: 200 },
				);
			}) as typeof fetch;

			try {
				const adapter = getDiscoveryAdapter(testCase.provider);
				const models = await adapter.fetchModels("test-key");

				assert.equal(calls[0]?.url, testCase.url);
				assert.deepEqual(calls[0]?.headers, {
					Authorization: "Bearer test-key",
				});
				assert.equal(models[0]?.id, testCase.model);
				assert.equal(models[0]?.provider, testCase.provider);
				assert.equal(models[0]?.api, testCase.api);
				assert.equal(models[0]?.baseUrl, testCase.baseUrl);
				assert.equal(models[0]?.contextWindow, 262144);
				assert.equal(models[0]?.maxTokens, 32768);
				assert.equal(models[0]?.reasoning, true);
				assert.deepEqual(models[0]?.input, ["text", "image"]);
			} finally {
				globalThis.fetch = originalFetch;
			}
		}
	});
});
