/**
 * Tests for the ask_user_questions gate (bootstrap/ask-gate.ts).
 *
 * Because node:test does not enable --experimental-test-module-mocks,
 * we test the gate logic via a locally-injectable mirror of the function
 * (same shape as gateAskUserQuestions) and also verify structural invariants
 * in the source text to catch drift between the real function and the contract.
 *
 * Gate contract:
 *   - Returns { allow: true } when not in autonomous mode (isAutoActive=false)
 *   - Returns { allow: true } when canAskUser=true (auto/step mode)
 *   - Returns { allow: false, reason } when isAutoActive=true + canAskUser=false
 *   - The reason mentions Tier 1, Tier 2, and a structured-blocker exit
 */

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

// ─── Local gate mirror (injectable deps) ────────────────────────────────────

interface AskGateDecision {
	allow: boolean;
	reason?: string;
}

/**
 * Mirror of gateAskUserQuestions with injectable state — used to unit-test
 * the gate logic without real module state or module mocking.
 */
function makeGate(
	isAutoActive: boolean,
	canAskUser: boolean,
): (payload: unknown) => AskGateDecision {
	return function gateAskUserQuestions(
		_questionPayload: unknown,
	): AskGateDecision {
		if (!isAutoActive || canAskUser) {
			return { allow: true };
		}

		const reason =
			"ask_user_questions is forbidden in autonomous mode. " +
			"Resolve via Tier 1 (code/sift/source files/.sf/KNOWLEDGE.md/.sf/DECISIONS.md) " +
			"or Tier 2 (WebSearch/WebFetch/Context7). " +
			"If the question is genuinely user-only (a preference, intent, design choice), " +
			"exit with a structured blocker message naming the unresolved ambiguity instead of calling this tool.";

		return { allow: false, reason };
	};
}

// ─── Source-level structural assertions ─────────────────────────────────────

const src = readFileSync(
	join(import.meta.dirname, "..", "bootstrap", "ask-gate.ts"),
	"utf-8",
);

describe("ask-gate: source structural invariants", () => {
	it("exports gateAskUserQuestions", () => {
		assert.ok(
			src.includes("export function gateAskUserQuestions"),
			"ask-gate.ts must export gateAskUserQuestions",
		);
	});

	it("exports AskGateDecision interface", () => {
		assert.ok(
			src.includes("export interface AskGateDecision"),
			"ask-gate.ts must export AskGateDecision interface",
		);
	});

	it("imports isAutoActive and isCanAskUser from auto.js", () => {
		assert.ok(
			src.includes("isAutoActive") && src.includes("isCanAskUser"),
			"ask-gate.ts must import isAutoActive and isCanAskUser",
		);
	});

	it("mentions Tier 1 in the rejection reason", () => {
		assert.ok(src.includes("Tier 1"), `ask-gate.ts must mention "Tier 1"`);
	});

	it("mentions Tier 2 in the rejection reason", () => {
		assert.ok(src.includes("Tier 2"), `ask-gate.ts must mention "Tier 2"`);
	});

	it("mentions structured blocker message in the rejection reason", () => {
		assert.ok(
			src.includes("structured blocker message"),
			`ask-gate.ts must mention "structured blocker message"`,
		);
	});

	it("calls logWarning on block", () => {
		assert.ok(
			src.includes("logWarning"),
			"ask-gate.ts must call logWarning when blocking",
		);
	});
});

// ─── Behavioural tests via injectable mirror ────────────────────────────────

describe("gateAskUserQuestions: allow path", () => {
	it("returns { allow: true } when auto is not active", () => {
		const gate = makeGate(false, false);
		const decision = gate({ questions: ["What is the version?"] });
		assert.equal(decision.allow, true);
		assert.equal(decision.reason, undefined);
	});

	it("returns { allow: true } when canAskUser=true (auto mode)", () => {
		const gate = makeGate(true, true);
		const decision = gate({ questions: ["What is the version?"] });
		assert.equal(decision.allow, true);
		assert.equal(decision.reason, undefined);
	});

	it("returns { allow: true } when not active and canAskUser=false", () => {
		const gate = makeGate(false, false);
		const decision = gate("plain string payload");
		assert.equal(decision.allow, true);
	});
});

describe("gateAskUserQuestions: block path (autonomous mode)", () => {
	const gate = makeGate(true, false);

	it("returns { allow: false } when isAutoActive=true and canAskUser=false", () => {
		const decision = gate({ questions: ["What is the repo URL?"] });
		assert.equal(decision.allow, false);
	});

	it("provides a reason string when blocking", () => {
		const decision = gate({ questions: ["Preferred code style?"] });
		assert.ok(typeof decision.reason === "string" && decision.reason.length > 0);
	});

	it("reason mentions Tier 1", () => {
		const { reason } = gate({ questions: ["What version is library X?"] });
		assert.ok(reason?.includes("Tier 1"), `reason must mention Tier 1: ${reason}`);
	});

	it("reason mentions Tier 2", () => {
		const { reason } = gate({ questions: ["What version is library X?"] });
		assert.ok(reason?.includes("Tier 2"), `reason must mention Tier 2: ${reason}`);
	});

	it("reason mentions structured blocker message", () => {
		const { reason } = gate({ questions: ["What is the project intent?"] });
		assert.ok(
			reason?.includes("structured blocker message"),
			`reason must mention structured blocker message: ${reason}`,
		);
	});

	it("works with non-object payloads", () => {
		const decision = gate("raw string question");
		assert.equal(decision.allow, false);
		assert.ok(decision.reason);
	});
});
