import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { atomicWriteSync } from "./atomic-write.js";
import { getErrorMessage } from "./error-utils.js";
import { sfRoot } from "./paths.js";
/** Schema version for production mutation approval JSON artifacts. */
export const PRODUCTION_MUTATION_APPROVAL_SCHEMA_VERSION = 1;
function unitId(unit) {
	return `${unit.milestoneId}/${unit.sliceId}/${unit.taskId}`;
}
function safePathSegment(value) {
	return value.replace(/[^A-Za-z0-9_.-]/g, "_");
}
export function productionMutationApprovalPath(basePath, unit) {
	const fileName = [
		safePathSegment(unit.milestoneId),
		safePathSegment(unit.sliceId),
		safePathSegment(unit.taskId),
	].join("-");
	return join(
		sfRoot(basePath),
		"approvals",
		"production-mutations",
		`${fileName}.json`,
	);
}
export function buildProductionMutationApprovalTemplate(unit) {
	return {
		schemaVersion: PRODUCTION_MUTATION_APPROVAL_SCHEMA_VERSION,
		unitId: unitId(unit),
		status: "pending",
		risk: "production-unified-failover-post",
		task: {
			title: unit.taskTitle,
			text: unit.taskText,
		},
		approval: {
			approved: false,
			approvedBy: "",
			approverType: "",
			approvedAt: "",
			safeServerId: "",
			safeVmNames: [],
			targetSelectionPlan: "",
			preMutationChecks: [],
			cleanupPlan: "",
			rollbackPlan: "",
			notes: "",
		},
		instructions: [
			"Human and LLM approvals are both valid when they name a safe non-customer-impacting target or a constrained target-selection plan.",
			"Fill status, approval.approved, approvedBy, approverType, approvedAt, safeServerId or targetSelectionPlan, safeVmNames or targetSelectionPlan, cleanupPlan, and rollbackPlan.",
			"Then rerun sf headless autonomous.",
		],
	};
}
function normalizedTask(unit) {
	return [unit.taskTitle, unit.taskText].join("\n").toLowerCase();
}
/**
 * Assess whether LLM auto-approval criteria are met for a production mutation.
 * Returns approved=true only if all safety constraints are present in task text.
 */
export function assessLlmProductionMutationApproval(unit) {
	const text = normalizedTask(unit);
	const reasons = [];
	if (
		!(
			text.includes("status=pending") ||
			text.includes("pending state") ||
			text.includes("pending command") ||
			text.includes("command row")
		)
	) {
		reasons.push("task is not constrained to pending command verification");
	}
	if (
		!(
			text.includes("do not trigger actual failover") ||
			text.includes("do not trigger real failover")
		)
	) {
		reasons.push("task does not explicitly forbid actual failover");
	}
	if (
		!(
			text.includes("routing table is unchanged") ||
			text.includes("gateway routing table is unchanged") ||
			text.includes("no active_dr")
		)
	) {
		reasons.push("task does not require no routing/runtime failover changes");
	}
	if (!(text.includes("test server_id") || text.includes("test server id"))) {
		reasons.push("task does not require a test server target");
	}
	if (!(text.includes("vm_names") || text.includes("test vm"))) {
		reasons.push("task does not require a test VM target");
	}
	return { approved: reasons.length === 0, reasons };
}
/**
 * Build a fully approved production mutation approval using LLM policy.
 * Caller must verify assessLlmProductionMutationApproval() returned approved=true first.
 */
export function buildLlmProductionMutationApproval(
	unit,
	approvedAt = new Date(),
) {
	return {
		...buildProductionMutationApprovalTemplate(unit),
		status: "approved",
		approval: {
			approved: true,
			approvedBy: "sf-llm:auto-production-mutation-policy",
			approverType: "llm",
			approvedAt: approvedAt.toISOString(),
			safeServerId: "agent-selected-non-customer-impacting-test-server",
			safeVmNames: ["agent-selected-non-customer-impacting-test-vm"],
			targetSelectionPlan:
				"Before POST, inspect portal/DB state and select only a non-customer-impacting test server_id and test VM. If no safe test target exists, stop and report instead of posting.",
			preMutationChecks: [
				"Confirm the task is pending-command-only validation.",
				"Confirm the selected target is not a real customer workload.",
				"Confirm no actual failover, active_dr update, or gateway route change is expected.",
				"Capture the command id created by the smoke POST for cleanup/audit.",
			],
			cleanupPlan:
				"After evidence capture, leave no actionable production failover command behind. If the row remains pending and a supported cancel/delete path exists, cancel/delete that smoke command; otherwise stop and escalate with the command id before any agent can execute it.",
			rollbackPlan:
				"Do not mutate gateway routing or active_dr state. If any route or active_dr state changes, stop immediately, restore the previous route/state from captured pre-checks, and report the incident.",
			notes:
				"LLM auto-approval is limited to this pending-command-only smoke test contract. It is not approval for real VM failover.",
		},
	};
}
/**
 * Assess and atomically write an LLM-approved production mutation approval if criteria match.
 * Returns approved=false with reasons if any safety constraint is missing.
 */
export function approveProductionMutationWithLlmPolicy(
	basePath,
	unit,
	approvedAt = new Date(),
) {
	const path = productionMutationApprovalPath(basePath, unit);
	const assessment = assessLlmProductionMutationApproval(unit);
	if (!assessment.approved) {
		return {
			path,
			approved: false,
			reasons: assessment.reasons,
			wrote: false,
		};
	}
	atomicWriteSync(
		path,
		JSON.stringify(
			buildLlmProductionMutationApproval(unit, approvedAt),
			null,
			2,
		) + "\n",
	);
	return { path, approved: true, reasons: [], wrote: true };
}
/**
 * Create an empty approval template if it doesn't exist. No-op if already present.
 */
export function ensureProductionMutationApprovalTemplate(basePath, unit) {
	const path = productionMutationApprovalPath(basePath, unit);
	if (existsSync(path)) return { path, created: false };
	atomicWriteSync(
		path,
		JSON.stringify(buildProductionMutationApprovalTemplate(unit), null, 2) +
			"\n",
	);
	return { path, created: true };
}
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
function nonEmptyString(value) {
	return typeof value === "string" && value.trim().length > 0;
}
function nonEmptyStringArray(value) {
	return (
		Array.isArray(value) &&
		value.length > 0 &&
		value.every((item) => nonEmptyString(item))
	);
}
/**
 * Validate a parsed approval JSON against schema and safety constraints.
 * Returns approved=false with reasons if any field is missing or invalid.
 */
export function validateProductionMutationApproval(data, unit) {
	const reasons = [];
	if (!isRecord(data)) {
		return { approved: false, reasons: ["approval file is not a JSON object"] };
	}
	if (data.schemaVersion !== PRODUCTION_MUTATION_APPROVAL_SCHEMA_VERSION) {
		reasons.push(
			`schemaVersion must be ${PRODUCTION_MUTATION_APPROVAL_SCHEMA_VERSION}`,
		);
	}
	if (data.unitId !== unitId(unit)) {
		reasons.push(`unitId must be ${unitId(unit)}`);
	}
	if (data.status !== "approved") {
		reasons.push("status must be approved");
	}
	if (
		!nonEmptyString(data.risk) ||
		data.risk !== "production-unified-failover-post"
	) {
		reasons.push("risk must be production-unified-failover-post");
	}
	const approval = data.approval;
	if (!isRecord(approval)) {
		reasons.push("approval must be an object");
		return { approved: false, reasons };
	}
	if (approval.approved !== true) {
		reasons.push("approval.approved must be true");
	}
	if (
		"approverType" in approval &&
		approval.approverType !== "" &&
		approval.approverType !== "human" &&
		approval.approverType !== "llm"
	) {
		reasons.push("approval.approverType must be human or llm");
	}
	for (const field of [
		"approvedBy",
		"approvedAt",
		"cleanupPlan",
		"rollbackPlan",
	]) {
		if (!nonEmptyString(approval[field])) {
			reasons.push(`approval.${field} is required`);
		}
	}
	const hasConcreteTarget =
		nonEmptyString(approval.safeServerId) &&
		nonEmptyStringArray(approval.safeVmNames);
	const hasTargetSelectionPlan = nonEmptyString(approval.targetSelectionPlan);
	if (!hasConcreteTarget && !hasTargetSelectionPlan) {
		reasons.push(
			"approval must include safeServerId and safeVmNames, or targetSelectionPlan",
		);
	}
	return { approved: reasons.length === 0, reasons };
}
/**
 * Read and validate approval status from disk. Returns rejected reasons if validation fails.
 */
export function readProductionMutationApprovalStatus(basePath, unit) {
	const path = productionMutationApprovalPath(basePath, unit);
	if (!existsSync(path)) {
		return { path, approved: false, reasons: ["approval file is missing"] };
	}
	try {
		const data = JSON.parse(readFileSync(path, "utf-8"));
		const result = validateProductionMutationApproval(data, unit);
		return { path, approved: result.approved, reasons: result.reasons };
	} catch (error) {
		const message = getErrorMessage(error);
		return {
			path,
			approved: false,
			reasons: [`approval file is not valid JSON: ${message}`],
		};
	}
}
