import { describe, expect, test } from "vitest";
import {
	isSolverModel,
	resolveSolverModel,
	resolveSolverModelCandidates,
	SOLVER_MODEL_DEFAULT,
	SOLVER_MODEL_FALLBACKS,
} from "../solver-model.js";

describe("solver-model invariants", () => {
	test("default is locked to kimi-k2.6 / kimi-coding", () => {
		// This is a PROTOCOL INVARIANT, not a tuning parameter. Changing the
		// default requires an ADR (see ADR-0079). If this test fails because
		// someone bumped the default, that's a load-bearing change and a code
		// review reject — re-read the ADR before re-running.
		expect(SOLVER_MODEL_DEFAULT).toEqual({
			provider: "kimi-coding",
			id: "kimi-k2.6",
		});
	});

	test("no fallback is a code-completion-only model", () => {
		// Code-completion models (Codestral, Devstral, the kimi-for-coding
		// alias) are the ones that broke the loop in the first place. They
		// must NEVER appear in the solver fallback chain.
		const forbidden = new Set([
			"codestral-latest",
			"devstral-latest",
			"kimi-for-coding",
		]);
		for (const candidate of SOLVER_MODEL_FALLBACKS) {
			expect(forbidden.has(candidate.id)).toBe(false);
		}
	});
});

describe("resolveSolverModel", () => {
	test("with no preferences returns the pinned default", () => {
		expect(resolveSolverModel()).toEqual(SOLVER_MODEL_DEFAULT);
		expect(resolveSolverModel(undefined)).toEqual(SOLVER_MODEL_DEFAULT);
		expect(resolveSolverModel({})).toEqual(SOLVER_MODEL_DEFAULT);
	});

	test("ignores router/benchmark/learning state (no opt-in == default)", () => {
		// Even with the kitchen sink of unrelated preference fields,
		// resolveSolverModel must NOT consult any of them. Only an explicit
		// preferences.autonomousSolver.model entry can override.
		const preferences = {
			currentModel: { provider: "mistral", id: "codestral-latest" },
			modelRouter: {
				lastSelection: "google-gemini-cli/gemini-3-flash-preview",
			},
			benchmarkSelector: { winner: "kimi-for-coding" },
			learning: { blender: { recommended: "kimi-k2.5" } },
		};
		expect(resolveSolverModel(preferences)).toEqual(SOLVER_MODEL_DEFAULT);
	});

	test("respects an explicit object override", () => {
		const resolved = resolveSolverModel({
			autonomousSolver: {
				model: { provider: "anthropic", id: "claude-opus-4-7" },
			},
		});
		expect(resolved).toEqual({ provider: "anthropic", id: "claude-opus-4-7" });
	});

	test("accepts a string override in provider/model form", () => {
		const resolved = resolveSolverModel({
			autonomousSolver: { model: "anthropic/claude-sonnet-4-6" },
		});
		expect(resolved).toEqual({
			provider: "anthropic",
			id: "claude-sonnet-4-6",
		});
	});

	test("accepts a bare model id and keeps the default provider", () => {
		const resolved = resolveSolverModel({
			autonomousSolver: { model: "kimi-k2-thinking" },
		});
		expect(resolved).toEqual({
			provider: SOLVER_MODEL_DEFAULT.provider,
			id: "kimi-k2-thinking",
		});
	});

	test("ignores an empty-string override", () => {
		expect(resolveSolverModel({ autonomousSolver: { model: "" } })).toEqual(
			SOLVER_MODEL_DEFAULT,
		);
		expect(resolveSolverModel({ autonomousSolver: { model: "   " } })).toEqual(
			SOLVER_MODEL_DEFAULT,
		);
	});
});

describe("resolveSolverModelCandidates", () => {
	test("primary comes first, then fallback chain (de-duplicated)", () => {
		const candidates = resolveSolverModelCandidates();
		expect(candidates[0]).toEqual(SOLVER_MODEL_DEFAULT);
		expect(candidates.length).toBe(1 + SOLVER_MODEL_FALLBACKS.length);
	});

	test("override does not duplicate when also in fallback list", () => {
		const candidates = resolveSolverModelCandidates({
			autonomousSolver: { model: "anthropic/claude-opus-4-7" },
		});
		const opusEntries = candidates.filter(
			(c) => c.id === "claude-opus-4-7" && c.provider === "anthropic",
		);
		expect(opusEntries.length).toBe(1);
	});
});

describe("isSolverModel", () => {
	test("returns true for the pinned default", () => {
		expect(isSolverModel(SOLVER_MODEL_DEFAULT)).toBe(true);
	});

	test("returns false for a routed executor model", () => {
		expect(isSolverModel({ provider: "mistral", id: "codestral-latest" })).toBe(
			false,
		);
		expect(
			isSolverModel({
				provider: "google-gemini-cli",
				id: "gemini-3-flash-preview",
			}),
		).toBe(false);
	});

	test("returns false for null / malformed inputs", () => {
		expect(isSolverModel(null)).toBe(false);
		expect(isSolverModel(undefined)).toBe(false);
		expect(isSolverModel({})).toBe(false);
	});
});
