import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
import { ChaosMonkey } from "./chaos-monkey.js";
import {
	writeTurnCloseoutGitRecord,
	writeTurnGitTransaction,
} from "./gitops.js";
import {
	acquireWriterToken,
	nextWriteRecord,
	releaseWriterToken,
} from "./writer.js";

const GITOPS_TIMEOUT_MS = 10_000;

function writeGitTransactionWithTimeout(args) {
	return Promise.race([
		writeTurnGitTransaction(args),
		new Promise((_, reject) =>
			setTimeout(
				() => reject(new Error("Git transaction timed out")),
				GITOPS_TIMEOUT_MS,
			),
		),
	]);
}
export function createTurnObserver(options) {
	let current = null;
	let writerToken = null;
	const phaseResults = [];
	const chaosMonkey = options.enableChaosMonkey ? new ChaosMonkey() : null;

	/**
	 * Enrich metadata with write sequence info when a writer token is active.
	 *
	 * Purpose: Provide audit/traceability by attaching sequence numbers to
	 * gitops and audit metadata. When no token is active (e.g., early in
	 * turn setup), returns metadata unchanged.
	 *
	 * @param {string} category — e.g., "gitops", "audit"
	 * @param {string} operation — e.g., "insert", "update"
	 * @param {object} [metadata] — caller-provided metadata
	 * @returns {object} metadata with optional writeSequence and writerTokenId
	 */
	function nextSequenceMetadata(category, operation, metadata) {
		if (!writerToken) return metadata ?? {};
		try {
			const record = nextWriteRecord({
				basePath: options.basePath,
				token: writerToken,
				category,
				operation,
				metadata,
			});
			return {
				...(metadata ?? {}),
				writeSequence: record.sequence.sequence,
				writerTokenId: record.writerToken.tokenId,
			};
		} catch (err) {
			// Token expired (TTL) or lost after process resume — re-acquire and retry once.
			if (err?.message?.includes("Writer token is not active")) {
				try {
					writerToken = acquireWriterToken({
						basePath: options.basePath,
						traceId: current?.traceId,
						turnId: current?.turnId,
					});
					const record = nextWriteRecord({
						basePath: options.basePath,
						token: writerToken,
						category,
						operation,
						metadata,
					});
					return {
						...(metadata ?? {}),
						writeSequence: record.sequence.sequence,
						writerTokenId: record.writerToken.tokenId,
						tokenRenewed: true,
					};
				} catch {
					// Re-acquisition failed — continue without sequence metadata rather than crashing.
					return metadata ?? {};
				}
			}
			return metadata ?? {};
		}
	}
	return {
		onTurnStart(contract) {
			if (chaosMonkey) chaosMonkey.strike("turn-start");
			current = {
				...contract,
				runControl: options.runControl,
				permissionProfile: options.permissionProfile,
			};
			phaseResults.length = 0;
			writerToken = acquireWriterToken({
				basePath: options.basePath,
				traceId: current.traceId,
				turnId: current.turnId,
			});
			if (options.enableGitops) {
				writeGitTransactionWithTimeout({
					basePath: options.basePath,
					traceId: current.traceId,
					turnId: current.turnId,
					unitType: current.unitType,
					unitId: current.unitId,
					stage: "turn-start",
					action: options.gitAction,
					push: options.gitPush,
					status: "ok",
					metadata: nextSequenceMetadata("gitops", "insert", {
						iteration: current.iteration,
						sidecarKind: current.sidecarKind,
						runControl: current.runControl,
						permissionProfile: current.permissionProfile,
					}),
				}).catch((err) => {
					console.error(
						`[loop-adapter] Git transaction failed: ${err.message}`,
					);
				});
			}
			if (options.enableAudit) {
				emitUokAuditEvent(
					options.basePath,
					buildAuditEnvelope({
						traceId: current.traceId,
						turnId: current.turnId,
						category: "orchestration",
						type: "turn-start",
						payload: nextSequenceMetadata("audit", "append", {
							iteration: current.iteration,
							unitType: current.unitType,
							unitId: current.unitId,
							sidecarKind: current.sidecarKind,
							runControl: current.runControl,
							permissionProfile: current.permissionProfile,
						}),
					}),
				);
			}
		},
		onPhaseResult(phase, action, data) {
			if (chaosMonkey) chaosMonkey.strike(`after-${phase}`);
			phaseResults.push({
				phase,
				action,
				ts: new Date().toISOString(),
				data,
			});
			if (!current || !options.enableGitops) return;
			if (phase === "dispatch") {
				writeGitTransactionWithTimeout({
					basePath: options.basePath,
					traceId: current.traceId,
					turnId: current.turnId,
					unitType: data?.unitType,
					unitId: data?.unitId,
					stage: "stage",
					action: options.gitAction,
					push: options.gitPush,
					status: "ok",
					metadata: nextSequenceMetadata("gitops", "update", { action }),
				}).catch((err) => {
					console.error(
						`[loop-adapter] Git transaction failed: ${err.message}`,
					);
				});
			}
			if (phase === "unit") {
				writeGitTransactionWithTimeout({
					basePath: options.basePath,
					traceId: current.traceId,
					turnId: current.turnId,
					unitType: data?.unitType,
					unitId: data?.unitId,
					stage: "checkpoint",
					action: options.gitAction,
					push: options.gitPush,
					status: "ok",
					metadata: nextSequenceMetadata("gitops", "update", { action }),
				}).catch((err) => {
					console.error(
						`[loop-adapter] Git transaction failed: ${err.message}`,
					);
				});
			}
			if (phase === "finalize") {
				writeGitTransactionWithTimeout({
					basePath: options.basePath,
					traceId: current.traceId,
					turnId: current.turnId,
					unitType: data?.unitType,
					unitId: data?.unitId,
					stage: "publish",
					action: options.gitAction,
					push: options.gitPush,
					status: "ok",
					metadata: nextSequenceMetadata("gitops", "update", { action }),
				}).catch((err) => {
					console.error(
						`[loop-adapter] Git transaction failed: ${err.message}`,
					);
				});
			}
		},
		onTurnResult(result) {
			const merged = {
				runControl: options.runControl,
				permissionProfile: options.permissionProfile,
				...result,
				phaseResults:
					result.phaseResults.length > 0
						? result.phaseResults
						: [...phaseResults],
			};
			if (options.enableAudit) {
				emitUokAuditEvent(
					options.basePath,
					buildAuditEnvelope({
						traceId: merged.traceId,
						turnId: merged.turnId,
						category: "orchestration",
						type: "turn-result",
						payload: nextSequenceMetadata("audit", "append", {
							unitType: merged.unitType,
							unitId: merged.unitId,
							status: merged.status,
							failureClass: merged.failureClass,
							error: merged.error,
							phaseCount: merged.phaseResults.length,
							runControl: merged.runControl,
							permissionProfile: merged.permissionProfile,
						}),
					}),
				);
			}
			if (options.enableGitops) {
				const closeout = merged.closeout ?? {
					traceId: merged.traceId,
					turnId: merged.turnId,
					unitType: merged.unitType,
					unitId: merged.unitId,
					status: merged.status,
					failureClass: merged.failureClass,
					gitAction: options.gitAction,
					gitPushed: options.gitPush,
					finishedAt: merged.finishedAt,
				};
				Promise.race([
					writeTurnCloseoutGitRecord(
						options.basePath,
						closeout,
						nextSequenceMetadata("gitops", "update", { action: "record" }),
					),
					new Promise((_, reject) =>
						setTimeout(
							() => reject(new Error("Git closeout timed out")),
							GITOPS_TIMEOUT_MS,
						),
					),
				]).catch((err) => {
					console.error(`[loop-adapter] Git closeout failed: ${err.message}`);
				});
			}
			if (writerToken) {
				releaseWriterToken(options.basePath, writerToken);
			}
			writerToken = null;
			current = null;
			phaseResults.length = 0;
		},
	};
}
