/**
 * jsonl-schema-versioning.test.mjs - SF-owned JSONL contract markers.
 *
 * Purpose: prove append-only SF runtime logs carry explicit schema versions
 * while legacy missing-version rows remain readable as version 1.
 */
import assert from "node:assert/strict";
import {
	mkdirSync,
	mkdtempSync,
	readdirSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, test } from "vitest";
import {
	appendAutonomousSolverCheckpoint,
	appendAutonomousSolverSteering,
	beginAutonomousSolverIteration,
	consumePendingAutonomousSolverSteering,
} from "../autonomous-solver.js";
import { triageTodoDump } from "../commands-todo.js";
import { emitJournalEvent, queryJournal } from "../journal.js";
import { appendJudgment, readJudgmentLog } from "../judgment-log.js";
import { ModelLearner } from "../model-learner.js";
import { createScheduleStore } from "../schedule/schedule-store.js";
import { closeDatabase, getDatabase } from "../sf-db.js";
import { buildAuditEnvelope, emitUokAuditEvent } from "../uok/audit.js";
import {
	parseParityEvents,
	writeParityHeartbeat,
} from "../uok/parity-report.js";
import { appendEvent, readEvents } from "../workflow-events.js";
import {
	_resetLogs,
	logError,
	readAuditLog,
	setLogBasePath,
} from "../workflow-logger.js";

const tmpDirs = [];

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

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-jsonl-versioning-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	return dir;
}

function readJsonl(path) {
	return readFileSync(path, "utf-8")
		.trim()
		.split("\n")
		.filter(Boolean)
		.map((line) => JSON.parse(line));
}

function makeScheduleEntry(overrides = {}) {
	return {
		id: "01HX0000000000000000000000",
		kind: "reminder",
		status: "pending",
		due_at: "2026-05-07T00:00:00.000Z",
		created_at: "2026-05-06T00:00:00.000Z",
		payload: { message: "check" },
		created_by: "user",
		...overrides,
	};
}

describe("SF JSONL schema versioning", () => {
	test("schedule_store_imports_legacy_jsonl_rows_as_version_1", () => {
		const project = makeProject();
		const store = createScheduleStore(project);
		const path = store._filePathForScope("project");
		mkdirSync(path.slice(0, path.lastIndexOf("/")), { recursive: true });

		writeFileSync(
			path,
			`${JSON.stringify(makeScheduleEntry({ id: "legacy" }))}\n`,
			"utf-8",
		);

		const [entry] = store.readEntries("project");
		assert.equal(entry.id, "legacy");
		assert.equal(entry.schemaVersion, 1);
	});

	test("journal_writes_schema_version_and_reads_legacy_rows", () => {
		const project = makeProject();
		emitJournalEvent(project, {
			ts: "2026-05-07T00:00:00.000Z",
			flowId: "flow-1",
			seq: 1,
			eventType: "iteration-start",
			data: { unitId: "M001" },
		});

		const path = join(project, ".sf", "journal", "2026-05-07.jsonl");
		assert.equal(readJsonl(path)[0].schemaVersion, 1);

		writeFileSync(
			path,
			`${JSON.stringify({
				ts: "2026-05-07T00:00:01.000Z",
				flowId: "legacy-flow",
				seq: 1,
				eventType: "dispatch-match",
				data: { unitId: "M002" },
			})}\n`,
			"utf-8",
		);

		const [entry] = queryJournal(project);
		assert.equal(entry.schemaVersion, 1);
		assert.equal(entry.flowId, "legacy-flow");
	});

	test("workflow_event_log_writes_schema_version_and_reads_legacy_rows", () => {
		const project = makeProject();
		appendEvent(project, { cmd: "plan", params: { milestoneId: "M001" } });

		const path = join(project, ".sf", "event-log.jsonl");
		assert.equal(readJsonl(path)[0].schemaVersion, 1);

		writeFileSync(
			path,
			`${JSON.stringify({
				v: 2,
				cmd: "legacy",
				params: { milestoneId: "M002" },
				hash: "abc",
			})}\n`,
			"utf-8",
		);

		const [event] = readEvents(path);
		assert.equal(event.schemaVersion, 1);
		assert.equal(event.cmd, "legacy");
	});

	test("workflow_audit_log_writes_schema_version_and_reads_legacy_rows", () => {
		const project = makeProject();
		setLogBasePath(project);
		logError("versioning-test", "audit failure", { id: "audit-1" });

		const path = join(project, ".sf", "audit-log.jsonl");
		assert.equal(readJsonl(path)[0].schemaVersion, 1);

		writeFileSync(
			path,
			`${JSON.stringify({
				ts: "2026-05-07T00:00:00.000Z",
				severity: "error",
				component: "legacy",
				message: "legacy error",
			})}\n`,
			"utf-8",
		);

		const [entry] = readAuditLog(project);
		assert.equal(entry.schemaVersion, 1);
		assert.equal(entry.component, "legacy");
	});

	test("uok_audit_and_parity_logs_write_schema_version_and_read_legacy_parity", () => {
		const project = makeProject();
		emitUokAuditEvent(
			project,
			buildAuditEnvelope({
				traceId: "trace-1",
				category: "orchestration",
				type: "test-event",
			}),
		);
		assert.equal(
			readJsonl(join(project, ".sf", "audit", "events.jsonl"))[0].schemaVersion,
			1,
		);

		writeParityHeartbeat(project, {
			ts: "2026-05-07T00:00:00.000Z",
			runId: "run-1",
			path: "uok-kernel",
			phase: "enter",
		});
		assert.equal(
			readJsonl(join(project, ".sf", "runtime", "uok-parity.jsonl"))[0]
				.schemaVersion,
			1,
		);

		const [legacy] = parseParityEvents(
			`${JSON.stringify({
				ts: "2026-05-07T00:00:01.000Z",
				runId: "legacy-run",
				path: "uok-kernel",
				phase: "exit",
				status: "ok",
			})}\n`,
		);
		assert.equal(legacy.schemaVersion, 1);
		assert.equal(legacy.runId, "legacy-run");
	});

	test("judgment_log_reads_entries_written_via_appendJudgment", () => {
		const project = makeProject();

		appendJudgment(project, {
			unitId: "M001/S01/T02",
			confidence: "low",
			decision: "legacy row",
		});

		const [entry] = readJudgmentLog(project, "M001");
		assert.equal(entry.schemaVersion, 1);
		assert.equal(entry.decision, "legacy row");
	});

	test("autonomous_solver_history_and_steering_write_schema_versions", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");
		appendAutonomousSolverCheckpoint(project, {
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			outcome: "continue",
			summary: "versioned checkpoint",
			completedItems: ["one"],
			remainingItems: ["two"],
			verificationEvidence: ["test"],
			pdd: {
				purpose: "prove schema marker",
				consumer: "solver loop",
				contract: "checkpoint row is versioned",
				failureBoundary: "unversioned history drifts",
				evidence: "jsonl row",
				nonGoals: "no solver behavior change",
				invariants: "legacy state remains readable",
				assumptions: "local fs works",
			},
		});

		assert.equal(
			readJsonl(
				join(
					project,
					".sf",
					"runtime",
					"autonomous-solver",
					"iterations.jsonl",
				),
			)[0].schemaVersion,
			1,
		);

		const steering = appendAutonomousSolverSteering(project, "prefer DB state");
		assert.equal(steering.schemaVersion, 1);
		const [consumed] = consumePendingAutonomousSolverSteering(project);
		assert.equal(consumed.schemaVersion, 1);
	});

	test("model_failure_log_writes_schema_version_and_reads_legacy_rows", () => {
		const project = makeProject();
		const learner = new ModelLearner(project);
		learner.logFailure("execute-task", "test-model", {
			reason: "timeout",
			timeout: true,
		});

		const path = join(project, ".sf", "model-failure-log.jsonl");
		assert.equal(readJsonl(path)[0].schemaVersion, 1);

		writeFileSync(
			path,
			`${JSON.stringify({
				timestamp: "2026-05-07T00:00:00.000Z",
				taskType: "execute-task",
				modelId: "legacy-model",
				reason: "quality",
			})}\n`,
			"utf-8",
		);

		const summary = learner.getFailureSummary("execute-task", "legacy-model");
		assert.equal(summary.reasons.quality, 1);
	});

	test("todo_triage_outputs_written_to_db", async () => {
		const project = makeProject();
		writeFileSync(join(project, "TODO.md"), "# TODO\n\nmake evals real\n");
		const response = JSON.stringify({
			summary: "triaged",
			eval_candidates: [
				{
					id: "eval.one",
					task_input: "run one",
					expected_behavior: "passes",
					evidence: "TODO.md",
				},
				{
					id: "eval.two",
					task_input: "run one",
					expected_behavior: "passes again",
					evidence: "TODO.md",
				},
			],
			implementation_tasks: ["add the task"],
			memory_requirements: ["remember the rule"],
			harness_suggestions: ["add harness"],
			docs_or_tests: ["document it"],
			unclear_notes: [],
		});

		await triageTodoDump(project, async () => response, {
			clear: false,
			backlog: true,
			date: new Date("2026-05-07T01:02:03.000Z"),
		});

		const db = getDatabase();
		assert.ok(db, "database should be open after triage");
		const evalRows = db.prepare("SELECT * FROM triage_evals").all();
		assert.ok(evalRows.length >= 2, "should have eval rows in DB");
		const itemRows = db.prepare("SELECT * FROM triage_items").all();
		assert.ok(itemRows.length > 0, "should have item rows in DB");
		const runRows = db.prepare("SELECT * FROM triage_runs").all();
		assert.ok(runRows.length > 0, "should have triage run in DB");

		// Backlog JSONL evidence file is still written (it is a human-readable artifact)
		const backlogDir = join(project, ".sf", "triage", "backlog");
		const [backlogFile] = readdirSync(backlogDir).filter((file) =>
			file.endsWith(".jsonl"),
		);
		const backlogRows = readJsonl(join(backlogDir, backlogFile));
		assert.ok(backlogRows.length > 0, "backlog JSONL should contain rows");
		for (const row of backlogRows) assert.equal(row.schemaVersion, 1);
	});
});
