/**
 * self-feedback-relations.test.mjs — causal links between entries
 * (sf-mp4rxkwx-jz0soh, v64 schema migration).
 *
 * Covers: linkEntries inserts; CHECK rejects self-loops; CHECK rejects
 * unknown relation_kind; getRelatedEntries returns both directions;
 * FK rejects when from_id doesn't exist; idempotent re-link returns false.
 */
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { recordSelfFeedback } from "../self-feedback.js";
import {
	closeDatabase,
	getRelatedEntries,
	linkEntries,
	openDatabase,
	SELF_FEEDBACK_RELATION_KINDS,
} from "../sf-db.js";

const tmpDirs = [];

afterEach(() => {
	closeDatabase();
	while (tmpDirs.length > 0) {
		const dir = tmpDirs.pop();
		if (dir) rmSync(dir, { recursive: true, force: true });
	}
});

function makeForgeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-relations-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	writeFileSync(
		join(dir, "package.json"),
		JSON.stringify({ name: "singularity-forge" }),
	);
	openDatabase(join(dir, ".sf", "sf.db"));
	return dir;
}

function fileTwo(project) {
	const a = recordSelfFeedback(
		{ kind: "gap:routing:foo", severity: "low", summary: "A" },
		project,
	);
	const b = recordSelfFeedback(
		{ kind: "gap:routing:bar", severity: "low", summary: "B" },
		project,
	);
	expect(a).toBeTruthy();
	expect(b).toBeTruthy();
	return [a.entry.id, b.entry.id];
}

describe("SELF_FEEDBACK_RELATION_KINDS", () => {
	test("exposes the closed enum of allowed relations", () => {
		expect(Array.from(SELF_FEEDBACK_RELATION_KINDS).sort()).toEqual([
			"blocks",
			"duplicate_of",
			"partial_fix_of",
			"root_cause_of",
			"supersedes",
		]);
	});
});

describe("linkEntries", () => {
	test("inserts a new (from, to, kind) triple and returns true", () => {
		const project = makeForgeProject();
		const [a, b] = fileTwo(project);
		expect(linkEntries(a, b, "supersedes")).toBe(true);
	});

	test("returns false on duplicate insert (idempotent)", () => {
		const project = makeForgeProject();
		const [a, b] = fileTwo(project);
		expect(linkEntries(a, b, "supersedes")).toBe(true);
		expect(linkEntries(a, b, "supersedes")).toBe(false);
	});

	test("allows multiple relation kinds between the same pair", () => {
		const project = makeForgeProject();
		const [a, b] = fileTwo(project);
		expect(linkEntries(a, b, "supersedes")).toBe(true);
		expect(linkEntries(a, b, "blocks")).toBe(true);
	});

	test("throws on an unknown relation_kind (writer-layer guard)", () => {
		const project = makeForgeProject();
		const [a, b] = fileTwo(project);
		expect(() => linkEntries(a, b, "invented")).toThrow(
			/invalid relation_kind/,
		);
	});

	test("throws on self-loop (CHECK constraint)", () => {
		const project = makeForgeProject();
		const [a] = fileTwo(project);
		expect(() => linkEntries(a, a, "supersedes")).toThrow();
	});

	test("throws on FK violation when from_id is unknown", () => {
		const project = makeForgeProject();
		const [, b] = fileTwo(project);
		expect(() => linkEntries("sf-does-not-exist", b, "supersedes")).toThrow();
	});
});

describe("getRelatedEntries", () => {
	test("returns outbound and inbound relations for the given id", () => {
		const project = makeForgeProject();
		const [a, b] = fileTwo(project);
		linkEntries(a, b, "supersedes");
		linkEntries(b, a, "blocks"); // b blocks a — b is from, a is to

		const aRelations = getRelatedEntries(a);
		expect(aRelations).toHaveLength(2);
		const aOutbound = aRelations.find((r) => r.direction === "outbound");
		const aInbound = aRelations.find((r) => r.direction === "inbound");
		expect(aOutbound).toEqual({
			id: b,
			relationKind: "supersedes",
			direction: "outbound",
		});
		expect(aInbound).toEqual({
			id: b,
			relationKind: "blocks",
			direction: "inbound",
		});

		const bRelations = getRelatedEntries(b);
		expect(bRelations).toHaveLength(2);
	});

	test("returns empty array when entry has no relations", () => {
		const project = makeForgeProject();
		const [a] = fileTwo(project);
		expect(getRelatedEntries(a)).toEqual([]);
	});
});
