/**
 * Test: Extension-Provided Models Integration (gap-5)
 *
 * Purpose: Verify that extensions can register custom models and
 * those models are discoverable and selectable by the model router.
 *
 * Consumer: Extensions like claude-code-cli register custom models;
 * autonomous mode dispatch must be able to select them.
 */

import { describe, expect, it } from "vitest";

// Mock extension models
const MOCK_EXTENSION_MODELS = [
	{
		id: "claude-sonnet-4-6",
		name: "Claude Sonnet 4.6 (via Claude Code)",
		reasoning: true,
		input: ["text", "image"],
		cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
		contextWindow: 1_000_000,
		maxTokens: 64_000,
	},
	{
		id: "claude-haiku-4-5",
		name: "Claude Haiku 4.5 (via Claude Code)",
		reasoning: true,
		input: ["text", "image"],
		cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
		contextWindow: 200_000,
		maxTokens: 64_000,
	},
];

const MOCK_PROVIDER = {
	name: "claude-code",
	displayName: "Claude Code",
	authMode: "externalCli",
	models: MOCK_EXTENSION_MODELS,
	isReady: () => true,
};

describe("Extension-Provided Models (gap-5)", () => {
	describe("Model Registration", () => {
		it("extension_can_register_models", () => {
			expect(MOCK_PROVIDER.models).toHaveLength(2);
			expect(MOCK_PROVIDER.models[0].id).toBe("claude-sonnet-4-6");
		});

		it("extension_models_have_required_fields", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.id).toBeDefined();
			expect(model.name).toBeDefined();
			expect(model.contextWindow).toBeDefined();
			expect(model.maxTokens).toBeDefined();
			expect(model.cost).toBeDefined();
		});

		it("extension_models_are_discoverable", () => {
			const discovered = MOCK_PROVIDER.models.filter(
				(m) => m.id === "claude-sonnet-4-6",
			);
			expect(discovered).toHaveLength(1);
		});
	});

	describe("Model Capability Detection", () => {
		it("detects_reasoning_capability", () => {
			const hasReasoning = MOCK_PROVIDER.models.filter((m) => m.reasoning);
			expect(hasReasoning.length).toBeGreaterThan(0);
		});

		it("detects_input_modalities", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.input).toContain("text");
			expect(model.input).toContain("image");
		});

		it("tracks_context_window_size", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.contextWindow).toBe(1_000_000);
		});

		it("tracks_max_tokens_per_model", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.maxTokens).toBe(64_000);
		});
	});

	describe("Cost Model", () => {
		it("extension_models_can_have_zero_cost", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.cost.input).toBe(0);
			expect(model.cost.output).toBe(0);
		});

		it("cost_structure_complete", () => {
			const model = MOCK_PROVIDER.models[0];
			expect(model.cost).toHaveProperty("input");
			expect(model.cost).toHaveProperty("output");
			expect(model.cost).toHaveProperty("cacheRead");
			expect(model.cost).toHaveProperty("cacheWrite");
		});
	});

	describe("Model Selection in Dispatch", () => {
		it("can_select_extension_model_by_id", () => {
			const modelId = "claude-sonnet-4-6";
			const selected = MOCK_PROVIDER.models.find((m) => m.id === modelId);
			expect(selected).toBeDefined();
			expect(selected.name).toContain("Sonnet");
		});

		it("can_filter_extension_models_by_capability", () => {
			const needsReasoning = true;
			const candidates = MOCK_PROVIDER.models.filter(
				(m) => m.reasoning === needsReasoning,
			);
			expect(candidates.length).toBeGreaterThan(0);
		});

		it("can_filter_extension_models_by_context_window", () => {
			const minContextWindow = 500_000;
			const suitable = MOCK_PROVIDER.models.filter(
				(m) => m.contextWindow >= minContextWindow,
			);
			expect(suitable).toContain(
				MOCK_PROVIDER.models.find((m) => m.id === "claude-sonnet-4-6"),
			);
		});
	});

	describe("Model Priority and Fallback", () => {
		it("can_rank_extension_models_by_preference", () => {
			const preferences = ["claude-sonnet-4-6", "claude-haiku-4-5"];
			const ranked = MOCK_PROVIDER.models.sort((a, b) => {
				const aIdx = preferences.indexOf(a.id);
				const bIdx = preferences.indexOf(b.id);
				return aIdx - bIdx;
			});
			expect(ranked[0].id).toBe("claude-sonnet-4-6");
		});

		it("supports_fallback_chain", () => {
			const preferredModels = ["claude-sonnet-4-6", "claude-haiku-4-5"];
			const fallback = (unavailable) => {
				return MOCK_PROVIDER.models.find(
					(m) => preferredModels.includes(m.id) && !unavailable.includes(m.id),
				);
			};

			const unavailable = ["claude-sonnet-4-6"];
			const selected = fallback(unavailable);
			expect(selected.id).toBe("claude-haiku-4-5");
		});
	});

	describe("Provider Integration", () => {
		it("extension_provider_is_ready", () => {
			expect(MOCK_PROVIDER.isReady()).toBe(true);
		});

		it("extension_provider_has_model_list", () => {
			expect(MOCK_PROVIDER.models).toBeDefined();
			expect(Array.isArray(MOCK_PROVIDER.models)).toBe(true);
		});

		it("multiple_extensions_can_coexist", () => {
			const provider1 = { name: "claude-code", models: MOCK_EXTENSION_MODELS };
			const provider2 = {
				name: "ollama",
				models: [
					{
						id: "llama-2",
						name: "Llama 2",
						contextWindow: 4096,
						maxTokens: 2048,
					},
				],
			};

			const allModels = [...provider1.models, ...provider2.models];
			expect(allModels).toHaveLength(3);
			expect(
				allModels.filter((m) => m.id === "claude-sonnet-4-6"),
			).toHaveLength(1);
			expect(allModels.filter((m) => m.id === "llama-2")).toHaveLength(1);
		});
	});

	describe("Model Metadata Completeness", () => {
		it("all_extension_models_have_description", () => {
			MOCK_PROVIDER.models.forEach((model) => {
				expect(model.name).toBeTruthy();
				expect(model.name.length).toBeGreaterThan(0);
			});
		});

		it("model_id_is_stable_and_unique", () => {
			const ids = MOCK_PROVIDER.models.map((m) => m.id);
			const uniqueIds = new Set(ids);
			expect(uniqueIds.size).toBe(ids.length);
		});

		it("model_costs_are_consistent", () => {
			MOCK_PROVIDER.models.forEach((model) => {
				const { input, output, cacheRead, cacheWrite } = model.cost;
				expect(typeof input).toBe("number");
				expect(typeof output).toBe("number");
				expect(typeof cacheRead).toBe("number");
				expect(typeof cacheWrite).toBe("number");
				expect(input).toBeGreaterThanOrEqual(0);
				expect(output).toBeGreaterThanOrEqual(0);
			});
		});
	});

	describe("Selective Model Access (gap-5 specific)", () => {
		it("user_can_prefer_extension_models_over_builtin", () => {
			const preferences = {
				preferred: ["claude-code/claude-sonnet-4-6"],
				blocked: ["openai/gpt-4"],
			};

			const candidates = [
				...MOCK_PROVIDER.models,
				{ id: "gpt-4", provider: "openai", name: "GPT-4" },
			];

			const allowed = candidates.filter(
				(m) => !preferences.blocked.includes(`${MOCK_PROVIDER.name}/${m.id}`),
			);

			expect(allowed).toContain(
				MOCK_PROVIDER.models.find((m) => m.id === "claude-sonnet-4-6"),
			);
		});

		it("extension_models_can_be_blocked", () => {
			const blocklist = ["claude-code/claude-haiku-4-5"];
			const filtered = MOCK_PROVIDER.models.filter(
				(m) => !blocklist.includes(`${MOCK_PROVIDER.name}/${m.id}`),
			);

			expect(filtered.find((m) => m.id === "claude-sonnet-4-6")).toBeDefined();
			expect(filtered.find((m) => m.id === "claude-haiku-4-5")).toBeUndefined();
		});

		it("can_route_to_extension_model_by_capability", () => {
			const _needsImage = true;
			const suitable = MOCK_PROVIDER.models.filter((m) =>
				m.input.includes("image"),
			);
			expect(suitable).toContain(
				MOCK_PROVIDER.models.find((m) => m.id === "claude-sonnet-4-6"),
			);
		});
	});

	describe("Error Handling", () => {
		it("handles_missing_extension_model", () => {
			const modelId = "nonexistent-model";
			const found = MOCK_PROVIDER.models.find((m) => m.id === modelId);
			expect(found).toBeUndefined();
		});

		it("gracefully_handles_extension_unavailable", () => {
			const provider = { ...MOCK_PROVIDER, isReady: () => false };
			const fallback = provider.isReady() ? provider.models : [];

			expect(fallback).toHaveLength(0);
		});

		it("validates_model_structure", () => {
			const validate = (model) => {
				return Boolean(
					model.id &&
						model.name &&
						model.contextWindow &&
						model.maxTokens &&
						model.cost,
				);
			};

			MOCK_PROVIDER.models.forEach((model) => {
				expect(validate(model)).toBe(true);
			});
		});
	});

	describe("Integration with Auto-Dispatch", () => {
		it("extension_models_included_in_candidate_pool", () => {
			const candidates = [
				{ id: "gpt-4", provider: "openai", score: 0.85 },
				...MOCK_PROVIDER.models.map((m) => ({
					...m,
					provider: "claude-code",
					score: 0.8,
				})),
			];

			expect(
				candidates.find((m) => m.provider === "claude-code"),
			).toBeDefined();
			expect(candidates).toHaveLength(3);
		});

		it("extension_model_scoring_works", () => {
			const _model = MOCK_PROVIDER.models[0];
			const score = 0.85; // hypothetical dispatch score

			expect(score).toBeGreaterThan(0);
			expect(score).toBeLessThanOrEqual(1);
		});
	});
});
