import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, it } from "vitest";
import {
	_getAdapter,
	closeDatabase,
	getSlice,
	insertMilestone,
	insertSlice,
	insertTask,
	openDatabase,
	updateSliceStatus,
} from "../sf-db.ts";
import { handleCompleteSlice } from "../tools/complete-slice.ts";
import type { CompleteSliceParams } from "../types.ts";

function tempDbPath(): string {
	const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sf-complete-slice-"));
	return path.join(dir, "test.db");
}

function cleanup(dbPath: string): void {
	closeDatabase();
	try {
		const dir = path.dirname(dbPath);
		for (const f of fs.readdirSync(dir)) {
			fs.unlinkSync(path.join(dir, f));
		}
		fs.rmdirSync(dir);
	} catch {
		// best effort
	}
}

function cleanupDir(dirPath: string): void {
	try {
		fs.rmSync(dirPath, { recursive: true, force: true });
	} catch {
		// best effort
	}
}

function createTempProject(): { basePath: string; roadmapPath: string } {
	const basePath = fs.mkdtempSync(path.join(os.tmpdir(), "sf-slice-handler-"));
	const sliceDir = path.join(basePath, ".sf", "milestones", "M001", "slices", "S01");
	const tasksDir = path.join(sliceDir, "tasks");
	fs.mkdirSync(tasksDir, { recursive: true });

	const roadmapPath = path.join(basePath, ".sf", "milestones", "M001", "M001-ROADMAP.md");
	fs.writeFileSync(
		roadmapPath,
		`# M001: Test Milestone

## Slices

- [ ] **S01: Test Slice** \`risk:medium\` \`depends:[]\`
  - After this: basic functionality works

- [ ] **S02: Second Slice** \`risk:low\` \`depends:[S01]\`
  - After this: advanced stuff
`,
	);

	return { basePath, roadmapPath };
}

function makeValidSliceParams(): CompleteSliceParams {
	return {
		sliceId: "S01",
		milestoneId: "M001",
		sliceTitle: "Test Slice",
		oneLiner: "Implemented test slice with full coverage",
		narrative:
			"Built the handler, registered the tool, and wrote comprehensive tests.",
		verification: "All 8 test sections pass with 0 failures.",
		deviations: "None.",
		knownLimitations: "None.",
		followUps: "None.",
		keyFiles: ["src/tools/complete-slice.ts", "src/bootstrap/db-tools.ts"],
		keyDecisions: ["D001"],
		patternsEstablished: [
			"SliceRow/rowToSlice follows same pattern as TaskRow/rowToTask",
		],
		observabilitySurfaces: ["SELECT status FROM slices shows completion state"],
		provides: ["complete_slice handler", "sf_slice_complete tool"],
		requirementsSurfaced: [],
		drillDownPaths: ["milestones/M001/slices/S01/tasks/T01-SUMMARY.md"],
		affects: ["S02"],
		requirementsAdvanced: [
			{ id: "R001", how: "Handler validates task completion" },
		],
		requirementsValidated: [],
		requirementsInvalidated: [],
		filesModified: [
			{ path: "src/tools/complete-slice.ts", description: "Handler implementation" },
			{ path: "src/bootstrap/db-tools.ts", description: "Tool registration" },
		],
		requires: [],
		uatContent: `## Smoke Test

Run the test suite and verify all assertions pass.

## Test Cases

### 1. Handler happy path

1. Insert complete tasks in DB
2. Call handleCompleteSlice()
3. **Expected:** SUMMARY.md + UAT.md written, roadmap checkbox toggled, DB updated`,
	};
}

describe("complete-slice: schema v6 migration", () => {
	it("schema version should be 21", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const adapter = _getAdapter()!;
		const versionRow = adapter
			.prepare("SELECT MAX(version) as v FROM schema_version")
			.get();
		assert.strictEqual(versionRow?.["v"], 21);

		cleanup(dbPath);
	});

	it("slices table has full_summary_md column", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const adapter = _getAdapter()!;
		const cols = adapter.prepare("PRAGMA table_info(slices)").all();
		const colNames = cols.map((c) => c["name"] as string);
		assert.ok(colNames.includes("full_summary_md"));

		cleanup(dbPath);
	});

	it("slices table has full_uat_md column", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const adapter = _getAdapter()!;
		const cols = adapter.prepare("PRAGMA table_info(slices)").all();
		const colNames = cols.map((c) => c["name"] as string);
		assert.ok(colNames.includes("full_uat_md"));

		cleanup(dbPath);
	});
});

describe("complete-slice: getSlice/updateSliceStatus accessors", () => {
	it("getSlice returns correct row", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001", title: "Test Slice", risk: "high" });

		const slice = getSlice("M001", "S01");
		assert.ok(slice !== null);
		assert.strictEqual(slice!.id, "S01");
		assert.strictEqual(slice!.milestone_id, "M001");
		assert.strictEqual(slice!.title, "Test Slice");
		assert.strictEqual(slice!.risk, "high");
		assert.strictEqual(slice!.status, "pending");
		assert.strictEqual(slice!.completed_at, null);
		assert.strictEqual(slice!.full_summary_md, "");
		assert.strictEqual(slice!.full_uat_md, "");

		cleanup(dbPath);
	});

	it("getSlice returns null for non-existent slice", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });

		const noSlice = getSlice("M001", "S99");
		assert.strictEqual(noSlice, null);

		cleanup(dbPath);
	});

	it("updateSliceStatus changes status and completed_at", () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });

		const now = new Date().toISOString();
		updateSliceStatus("M001", "S01", "complete", now);
		const updated = getSlice("M001", "S01");
		assert.strictEqual(updated!.status, "complete");
		assert.strictEqual(updated!.completed_at, now);

		cleanup(dbPath);
	});
});

describe("complete-slice: handler happy path", () => {
	it("writes summary and UAT files and updates roadmap and DB", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const { basePath, roadmapPath } = createTempProject();

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });
		insertSlice({ id: "S02", milestoneId: "M001", title: "Second Slice" });
		insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 1" });
		insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 2" });

		const params = makeValidSliceParams();
		const result = await handleCompleteSlice(params, basePath);

		assert.ok(!("error" in result));
		if ("error" in result) return;

		assert.strictEqual(result.sliceId, "S01");
		assert.strictEqual(result.milestoneId, "M001");
		assert.ok(result.summaryPath.endsWith("S01-SUMMARY.md"));
		assert.ok(result.uatPath.endsWith("S01-UAT.md"));

		const summaryContent = fs.readFileSync(result.summaryPath, "utf-8");
		assert.ok(/^---\n/.test(summaryContent));
		assert.ok(/id: S01/.test(summaryContent));
		assert.ok(/parent: M001/.test(summaryContent));
		assert.ok(/milestone: M001/.test(summaryContent));
		assert.ok(/blocker_discovered: false/.test(summaryContent));
		assert.ok(/verification_result: passed/.test(summaryContent));

		const roadmapContent = fs.readFileSync(roadmapPath, "utf-8");
		assert.ok(/\besf S01 \b/.test(roadmapContent) || /\| S01 \|/.test(roadmapContent));

		const sliceAfter = getSlice("M001", "S01");
		assert.ok(sliceAfter !== null);
		assert.ok(sliceAfter!.full_summary_md.length > 0);
		assert.ok(sliceAfter!.full_uat_md.length > 0);
		assert.strictEqual(sliceAfter!.status, "complete");

		cleanupDir(basePath);
		cleanup(dbPath);
	});
});

describe("complete-slice: handler rejects incomplete tasks", () => {
	it("returns error when tasks are incomplete", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });
		insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 1" });
		insertTask({ id: "T02", sliceId: "S01", milestoneId: "M001", status: "pending", title: "Task 2" });

		const params = makeValidSliceParams();
		const result = await handleCompleteSlice(params, "/tmp/fake");

		assert.ok("error" in result);
		if (!("error" in result)) return;
		assert.ok(/incomplete tasks/.test(result.error));
		assert.ok(/T02/.test(result.error));

		cleanup(dbPath);
	});

	it("returns error when no tasks exist", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });

		const params = makeValidSliceParams();
		const result = await handleCompleteSlice(params, "/tmp/fake");

		assert.ok("error" in result);
		if (!("error" in result)) return;
		assert.ok(/no tasks found/.test(result.error));

		cleanup(dbPath);
	});
});

describe("complete-slice: handler validation errors", () => {
	it("returns error for empty sliceId", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const params = makeValidSliceParams();
		const r = await handleCompleteSlice({ ...params, sliceId: "" }, "/tmp/fake");

		assert.ok("error" in r);

		cleanup(dbPath);
	});

	it("returns error for empty milestoneId", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const params = makeValidSliceParams();
		const r = await handleCompleteSlice({ ...params, milestoneId: "" }, "/tmp/fake");

		assert.ok("error" in r);

		cleanup(dbPath);
	});
});

describe("complete-slice: handler idempotency", () => {
	it("second call returns error (slice already complete)", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const { basePath } = createTempProject();

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });
		insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 1" });

		const params = makeValidSliceParams();

		const r1 = await handleCompleteSlice(params, basePath);
		assert.ok(!("error" in r1));

		const r2 = await handleCompleteSlice(params, basePath);
		assert.ok("error" in r2);
		if ("error" in r2) {
			assert.ok(/already complete/.test(r2.error));
		}

		const adapter = _getAdapter()!;
		const sliceRows = adapter
			.prepare("SELECT * FROM slices WHERE milestone_id = 'M001' AND id = 'S01'")
			.all();
		assert.strictEqual(sliceRows.length, 1);

		cleanupDir(basePath);
		cleanup(dbPath);
	});
});

describe("complete-slice: handler with missing roadmap", () => {
	it("succeeds even without roadmap file", async () => {
		const dbPath = tempDbPath();
		openDatabase(dbPath);

		const basePath = fs.mkdtempSync(path.join(os.tmpdir(), "sf-no-roadmap-"));
		const sliceDir = path.join(basePath, ".sf", "milestones", "M001", "slices", "S01");
		fs.mkdirSync(sliceDir, { recursive: true });

		insertMilestone({ id: "M001" });
		insertSlice({ id: "S01", milestoneId: "M001" });
		insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", status: "complete", title: "Task 1" });

		const params = makeValidSliceParams();
		const result = await handleCompleteSlice(params, basePath);

		assert.ok(!("error" in result));
		if (!("error" in result)) {
			assert.ok(fs.existsSync(result.summaryPath));
			assert.ok(fs.existsSync(result.uatPath));
		}

		cleanupDir(basePath);
		cleanup(dbPath);
	});
});

describe("complete-slice: step 13 specifies write tool for PROJECT.md", () => {
	it("step 13 names the write tool when updating PROJECT.md", () => {
		const promptPath = path.join(
			path.dirname(new URL(import.meta.url).pathname),
			"..",
			"prompts",
			"complete-slice.md",
		);
		const prompt = fs.readFileSync(promptPath, "utf-8");

		const mentionsWriteTool =
			/PROJECT\.md.*\bwrite\b/i.test(prompt) ||
			/\bwrite\b.*PROJECT\.md/i.test(prompt);

		assert.ok(mentionsWriteTool);
	});
});
