import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import {
	_readCacheForTests,
	clearSliceRoutingForUnit,
	extractSliceScope,
	readStickyModelForUnit,
	recordSliceRouting,
} from "../slice-routing-cache.js";

let tempDirs = [];
function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-slice-routing-"));
	tempDirs.push(dir);
	return dir;
}
afterEach(() => {
	for (const dir of tempDirs) rmSync(dir, { recursive: true, force: true });
	tempDirs = [];
});

describe("extractSliceScope", () => {
	test("execute-task style unit id collapses to milestone/slice", () => {
		expect(extractSliceScope("M001-6377a4/S04/T02")).toBe("M001-6377a4/S04");
	});
	test("plan/complete slice ids stay as milestone/slice", () => {
		expect(extractSliceScope("M001-6377a4/S04")).toBe("M001-6377a4/S04");
	});
	test("milestone-only ids return the milestone", () => {
		expect(extractSliceScope("M001-6377a4")).toBe("M001-6377a4");
	});
	test("null/undefined/empty return null", () => {
		expect(extractSliceScope(null)).toBeNull();
		expect(extractSliceScope("")).toBeNull();
		expect(extractSliceScope(undefined)).toBeNull();
	});
});

describe("slice routing cache", () => {
	test("record + read round-trips", () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		const sticky = readStickyModelForUnit(
			project,
			"execute-task",
			"M001/S04/T02",
		);
		expect(sticky).toEqual({ provider: "moonshotai", id: "kimi-k2.6" });
	});

	test("sticky scoped per slice — different slice => no hit", () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		expect(
			readStickyModelForUnit(project, "execute-task", "M001/S05/T01"),
		).toBeNull();
	});

	test("clearSliceRoutingForUnit evicts only the matching slice", () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		recordSliceRouting(project, "execute-task", "M001/S05/T01", {
			provider: "anthropic",
			id: "claude-sonnet-4-6",
		});
		clearSliceRoutingForUnit(project, "M001/S04/T07");
		expect(
			readStickyModelForUnit(project, "execute-task", "M001/S04/T99"),
		).toBeNull();
		expect(
			readStickyModelForUnit(project, "execute-task", "M001/S05/T02"),
		).toEqual({ provider: "anthropic", id: "claude-sonnet-4-6" });
	});

	test("readStickyModelForUnit honors maxAgeMs", async () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		// Sleep past the retention window so age strictly exceeds maxAgeMs.
		await new Promise((resolve) => setTimeout(resolve, 25));
		expect(
			readStickyModelForUnit(project, "execute-task", "M001/S04/T02", {
				maxAgeMs: 10,
			}),
		).toBeNull();
	});

	test("returns null on missing basePath or unparseable unit id", () => {
		expect(
			readStickyModelForUnit("", "execute-task", "M001/S04/T01"),
		).toBeNull();
		const project = makeProject();
		expect(readStickyModelForUnit(project, "execute-task", "")).toBeNull();
		expect(readStickyModelForUnit(project, "execute-task", null)).toBeNull();
	});

	test("overwrite updates the slice entry in place", () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		recordSliceRouting(project, "execute-task", "M001/S04/T02", {
			provider: "anthropic",
			id: "claude-opus-4-7",
		});
		const cache = _readCacheForTests(project);
		const entries = Object.values(cache);
		expect(entries.length).toBe(1);
		expect(
			readStickyModelForUnit(project, "execute-task", "M001/S04/T03"),
		).toEqual({ provider: "anthropic", id: "claude-opus-4-7" });
	});

	test("clearSliceRoutingForUnit on the last entry removes the cache file", () => {
		const project = makeProject();
		recordSliceRouting(project, "execute-task", "M001/S04/T01", {
			provider: "moonshotai",
			id: "kimi-k2.6",
		});
		clearSliceRoutingForUnit(project, "M001/S04/T01");
		const cache = _readCacheForTests(project);
		expect(Object.keys(cache).length).toBe(0);
	});
});
