/**
 * verification-fail-closed.test.ts — Structural regression for Fix 3.
 *
 * Verifies that `runPostUnitVerification` in auto-verification.ts splits its
 * single monolithic try/catch into two zones:
 *
 *   Zone 1 (Gate machinery): loadEffectiveSFPreferences, getTask,
 *     runVerificationGate, captureRuntimeErrors, runDependencyAudit.
 *     → if this throws: log error, notify UI "error", call pauseAuto,
 *       return "pause".
 *
 *   Zone 2 (Ancillary): evidence writes, UOK gate runner calls,
 *     notification calls, auto-fix retry logic.
 *     → if this throws: log warning, return "continue".
 *
 * Testing strategy:
 *   node:test (this project) does NOT enable --experimental-test-module-mocks,
 *   so module-level mocking is unavailable (see graph-context.test.ts note).
 *   We verify the structural invariants by inspecting the compiled source text
 *   of auto-verification.ts directly, matching key patterns that encode the
 *   required behavior. This is the same approach used in
 *   auto-wrapup-inflight-guard.test.ts.
 *
 * Invariants checked:
 *   1. Zone 1 outer catch calls pauseAuto and returns "pause".
 *   2. Zone 1 outer catch uses logError (not logWarning) and the exact
 *      required message string.
 *   3. Zone 1 outer catch calls ctx.ui.notify with "error" level.
 *   4. Zone 2 inner catch returns "continue" (non-fatal fallback).
 *   5. Zone 2 inner catch uses logWarning (not logError).
 *   6. The two catches are distinct — machinery catch appears before
 *      ancillary catch in source order.
 */

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it } from 'vitest';

const src = readFileSync(
	join(import.meta.dirname, "..", "auto-verification.ts"),
	"utf-8",
);

// ─── helpers ────────────────────────────────────────────────────────────────

/** Find the nth occurrence of `needle` in `haystack`. Returns -1 if not found. */
function _nthIndex(haystack: string, needle: string, n: number): number {
	let idx = -1;
	for (let i = 0; i < n; i++) {
		idx = haystack.indexOf(needle, idx + 1);
		if (idx === -1) return -1;
	}
	return idx;
}

// ─── Zone 1: machinery catch invariants ────────────────────────────────────

describe("Zone 1 — gate machinery catch", () => {
	it("contains the required exact error message string", () => {
		assert.ok(
			src.includes(
				"verification-gate machinery error — pausing for human review",
			),
			'auto-verification.ts must contain the literal string "verification-gate machinery error — pausing for human review"',
		);
	});

	it("uses logError (not logWarning) in the machinery catch", () => {
		// Find the machinery catch block by locating "machineryErr"
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(
			machineryIdx !== -1,
			"machinery catch variable 'machineryErr' must be present",
		);

		// The next 400 characters of the catch block must contain logError
		const catchBlock = src.slice(machineryIdx, machineryIdx + 400);
		assert.ok(
			catchBlock.includes("logError"),
			"machinery catch must call logError (not logWarning) for infrastructure failures",
		);
		assert.ok(
			!catchBlock.includes("logWarning"),
			"machinery catch must NOT call logWarning — infrastructure failures are errors, not warnings",
		);
	});

	it("calls ctx.ui.notify with 'error' level in machinery catch", () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);
		const catchBlock = src.slice(machineryIdx, machineryIdx + 400);

		assert.ok(
			catchBlock.includes("ctx.ui.notify"),
			"machinery catch must notify the UI",
		);
		assert.ok(
			catchBlock.includes('"error"'),
			"machinery catch UI notification must use 'error' severity level",
		);
	});

	it("calls pauseAuto in the machinery catch", () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);
		const catchBlock = src.slice(machineryIdx, machineryIdx + 400);

		assert.ok(
			catchBlock.includes("pauseAuto"),
			"machinery catch must call pauseAuto to halt the auto-loop",
		);
	});

	it('returns "pause" in the machinery catch', () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);
		const catchBlock = src.slice(machineryIdx, machineryIdx + 400);

		assert.ok(
			catchBlock.includes('return "pause"'),
			'machinery catch must return "pause" so the caller halts the loop',
		);
	});
});

// ─── Zone 2: ancillary catch invariants ────────────────────────────────────

describe("Zone 2 — ancillary post-gate catch", () => {
	it("the ancillary catch uses logWarning (non-fatal path)", () => {
		// The ancillary catch uses 'err' as the binding (not 'machineryErr')
		// and must appear AFTER the machinery catch in source order.
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1, "machinery catch must be present");

		// Find the second catch block: look for "} catch (err)" after machineryIdx
		const ancillaryCatchIdx = src.indexOf("} catch (err)", machineryIdx);
		assert.ok(
			ancillaryCatchIdx !== -1,
			"ancillary catch '} catch (err)' must appear after the machinery catch",
		);

		const catchBlock = src.slice(ancillaryCatchIdx, ancillaryCatchIdx + 300);
		assert.ok(
			catchBlock.includes("logWarning"),
			"ancillary catch must use logWarning (non-fatal)",
		);
	});

	it('returns "continue" in the ancillary catch', () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);
		const ancillaryCatchIdx = src.indexOf("} catch (err)", machineryIdx);
		assert.ok(ancillaryCatchIdx !== -1);

		const catchBlock = src.slice(ancillaryCatchIdx, ancillaryCatchIdx + 300);
		assert.ok(
			catchBlock.includes('return "continue"'),
			'ancillary catch must return "continue" — post-gate errors are non-fatal',
		);
	});

	it("ancillary catch does NOT call pauseAuto", () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);
		const ancillaryCatchIdx = src.indexOf("} catch (err)", machineryIdx);
		assert.ok(ancillaryCatchIdx !== -1);

		const catchBlock = src.slice(ancillaryCatchIdx, ancillaryCatchIdx + 300);
		assert.ok(
			!catchBlock.includes("pauseAuto"),
			"ancillary catch must NOT call pauseAuto — post-gate errors do not halt the agent",
		);
	});
});

// ─── Structural ordering invariants ─────────────────────────────────────────

describe("Two-zone structural ordering", () => {
	it("machinery catch appears before ancillary catch in source", () => {
		const machineryIdx = src.indexOf("machineryErr");
		const ancillaryCatchIdx = src.indexOf("} catch (err)", machineryIdx + 1);
		assert.ok(machineryIdx !== -1, "'machineryErr' binding must exist");
		assert.ok(
			ancillaryCatchIdx !== -1,
			"'} catch (err)' must exist after machinery catch",
		);
		assert.ok(
			machineryIdx < ancillaryCatchIdx,
			"Zone 1 machinery catch must appear before Zone 2 ancillary catch",
		);
	});

	it("Zone 1 critical calls appear before the machinery catch", () => {
		const machineryIdx = src.indexOf("machineryErr");
		assert.ok(machineryIdx !== -1);

		// All Zone 1 calls must appear between function start and machineryErr
		const zone1Section = src.slice(
			src.indexOf("export async function runPostUnitVerification("),
			machineryIdx,
		);

		assert.ok(
			zone1Section.includes("loadEffectiveSFPreferences"),
			"loadEffectiveSFPreferences must be in Zone 1",
		);
		assert.ok(
			zone1Section.includes("runVerificationGate"),
			"runVerificationGate must be in Zone 1",
		);
		assert.ok(
			zone1Section.includes("captureRuntimeErrors"),
			"captureRuntimeErrors must be in Zone 1",
		);
		assert.ok(
			zone1Section.includes("runDependencyAudit"),
			"runDependencyAudit must be in Zone 1",
		);
	});

	it("logError is imported from workflow-logger", () => {
		assert.ok(
			src.includes("logError") && src.includes("workflow-logger"),
			"logError must be imported from workflow-logger",
		);
		// Verify the import line includes logError
		const importLine = src.slice(
			src.indexOf("import {"),
			src.indexOf('} from "./workflow-logger'),
		);
		assert.ok(
			importLine.includes("logError"),
			"logError must appear in the workflow-logger import",
		);
	});
});
