import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
import { sfRoot } from "./paths.js";
import {
	getDistinctGateIds,
	getGateCircuitBreaker,
	getUokMessageBusMetrics,
	getUokRuns,
	isDbAvailable,
} from "./sf-db.js";
import { writeUokDiagnostics } from "./uok/diagnostic-synthesis.js";
import { MessageBus } from "./uok/message-bus.js";
import { readUokMetrics, writeUokMetrics } from "./uok/metrics-exposition.js";
import {
	summarizeParityHealth,
	writeParityReport,
} from "./uok/parity-report.js";

function formatAge(value, nowMs = Date.now()) {
	const ms = Date.parse(String(value ?? ""));
	if (!Number.isFinite(ms)) return "unknown";
	const diffMs = Math.max(0, nowMs - ms);
	const minutes = Math.floor(diffMs / 60_000);
	if (minutes < 1) return "just now";
	if (minutes < 60) return `${minutes}m ago`;
	const hours = Math.floor(minutes / 60);
	if (hours < 48) return `${hours}h ago`;
	const days = Math.floor(hours / 24);
	return `${days}d ago`;
}

function formatTimestamp(value) {
	return value ? String(value) : "unknown";
}

function statusIcon(status) {
	if (status === "ok") return "ok";
	if (status === "error") return "error";
	if (status === "started") return "running";
	return status || "unknown";
}

export async function collectUokStatus(
	basePath = process.cwd(),
	nowMs = Date.now(),
) {
	await ensureDbOpen(basePath);
	const dbAvailable = isDbAvailable();
	let runs = [];
	if (dbAvailable) {
		runs = getUokRuns(20);
	}
	let report = null;
	let health = null;
	try {
		report = writeParityReport(basePath, nowMs);
		health = summarizeParityHealth(report);
	} catch {
		const reportPath = join(
			sfRoot(basePath),
			"runtime",
			"uok-parity-report.json",
		);
		if (existsSync(reportPath)) {
			try {
				const parsed = JSON.parse(readFileSync(reportPath, "utf-8"));
				report = parsed;
				health = summarizeParityHealth(parsed);
			} catch {
				report = null;
				health = null;
			}
		}
	}
	const lastRun = runs[0] ?? null;
	const lastErrorRun = runs.find((run) => run.status === "error") ?? null;
	const current = health?.current ?? {
		criticalMismatches: 0,
		missingExitEvents: 0,
		errorEvents: 0,
	};
	const historical = health?.historical ?? {
		criticalMismatches: 0,
		legacyMissingExitEvents: 0,
		errorEvents: 0,
		unmatchedRuns: 0,
	};
	const startupBlocked =
		current.criticalMismatches > 0 ||
		current.missingExitEvents > 0 ||
		current.errorEvents > 0;
	let diagnostics = null;
	try {
		diagnostics = writeUokDiagnostics(basePath, { nowMs });
	} catch {
		diagnostics = null;
	}
	let gateHealth = null;
	let metricsPath = null;
	try {
		const runner = new UokGateRunner();
		gateHealth = runner.getHealthSummary();
		metricsPath = writeUokMetrics(basePath);
	} catch {
		// gate health and metrics are best-effort
	}
	return {
		dbAvailable,
		generatedAt: new Date(nowMs).toISOString(),
		startupBlocked,
		healthStatus:
			startupBlocked || diagnostics?.verdict === "degraded" ? "degraded" : "ok",
		ledgerRunCount: report?.ledgerRunCount ?? runs.length,
		recentRuns: runs,
		lastRun,
		lastErrorRun,
		current,
		historical,
		diagnostics,
		gateHealth,
		metricsPath,
		reportPath: join(sfRoot(basePath), "runtime", "uok-parity-report.json"),
	};
}

export function formatUokStatus(status, nowMs = Date.now()) {
	const lines = ["UOK status", ""];
	lines.push(`Health: ${status.healthStatus}`);
	lines.push(`Startup gate: ${status.startupBlocked ? "blocked" : "clear"}`);
	lines.push(`DB ledger: ${status.dbAvailable ? "available" : "unavailable"}`);
	lines.push(`Ledger runs: ${status.ledgerRunCount}`);
	if (status.diagnostics) {
		lines.push(
			`Diagnostics: ${status.diagnostics.verdict} (${status.diagnostics.classification})`,
		);
		if (status.diagnostics.currentUnit) {
			const unit = status.diagnostics.currentUnit;
			lines.push(
				`Current unit: ${unit.unitType ?? "unknown"} ${unit.unitId ?? "unknown"} pid ${unit.pid ?? "unknown"}`,
			);
		}
		const firstIssue = status.diagnostics.issues?.[0];
		if (firstIssue) lines.push(`Diagnostic issue: ${firstIssue.code}`);
	}
	lines.push("");
	lines.push("Current:");
	lines.push(`  critical mismatches: ${status.current.criticalMismatches}`);
	lines.push(`  missing exits: ${status.current.missingExitEvents}`);
	lines.push(`  error events: ${status.current.errorEvents}`);
	lines.push("");
	lines.push("Historical:");
	lines.push(`  old mismatches: ${status.historical.criticalMismatches}`);
	lines.push(
		`  legacy missing exits: ${status.historical.legacyMissingExitEvents}`,
	);
	lines.push(`  old error events: ${status.historical.errorEvents}`);
	lines.push(`  stale unmatched runs: ${status.historical.unmatchedRuns}`);
	lines.push("");
	if (status.lastRun) {
		lines.push("Last run:");
		lines.push(`  id: ${status.lastRun.runId}`);
		lines.push(`  status: ${statusIcon(status.lastRun.status)}`);
		lines.push(`  path: ${status.lastRun.path || "unknown"}`);
		lines.push(
			`  started: ${formatTimestamp(status.lastRun.startedAt)} (${formatAge(status.lastRun.startedAt, nowMs)})`,
		);
		lines.push(`  ended: ${formatTimestamp(status.lastRun.endedAt)}`);
	} else {
		lines.push("Last run: none recorded in the DB ledger yet");
	}
	lines.push("");
	if (status.lastErrorRun) {
		lines.push("Last error:");
		lines.push(`  id: ${status.lastErrorRun.runId}`);
		lines.push(
			`  at: ${formatTimestamp(status.lastErrorRun.endedAt ?? status.lastErrorRun.updatedAt)}`,
		);
		lines.push(`  error: ${status.lastErrorRun.error ?? "unknown"}`);
	} else {
		lines.push("Last error: none in ledger");
	}
	lines.push("");
	if (status.gateHealth?.gates?.length > 0) {
		lines.push("Gate health (24h):");
		for (const g of status.gateHealth.gates) {
			const icon =
				g.circuitBreaker === "open"
					? "🔴"
					: g.circuitBreaker === "half-open"
						? "🟡"
						: "🟢";
			lines.push(
				`  ${icon} ${g.id}: ${g.pass} pass / ${g.fail} fail / ${g.retry} retry | cb: ${g.circuitBreaker}${g.failureStreak > 0 ? ` (streak ${g.failureStreak})` : ""}`,
			);
		}
		lines.push("");
	}
	if (status.metricsPath) {
		lines.push(`Metrics: ${status.metricsPath}`);
	}
	lines.push(`Report: ${status.reportPath}`);
	return lines.join("\n");
}

export async function handleUok(args, ctx) {
	const trimmed = args.trim();
	if (trimmed === "help" || trimmed === "--help") {
		ctx.ui.notify(
			"Usage: /uok [status|metrics|circuit-breakers|gates|messages|--json]\n\n  status            — UOK ledger health, last run, last error, historical drift, startup gate, and gate health\n  metrics           — Render Prometheus-format metrics to .sf/runtime/uok-metrics.prom and display\n  circuit-breakers  — List all circuit breaker states and failure streaks\n  gates             — List observed gate runs and circuit breaker state\n  messages          — Show message bus status\n  --json            — Same as status but outputs JSON",
			"info",
		);
		return;
	}
	if (trimmed === "metrics") {
		const basePath = process.cwd();
		const path = writeUokMetrics(basePath);
		const text = readUokMetrics(basePath);
		ctx.ui.notify(
			text ?? "No metrics available (DB unavailable or no gate data)",
			"info",
		);
		ctx.ui.notify(`Written to: ${path}`, "info");
		return;
	}
	if (trimmed === "circuit-breakers" || trimmed === "circuit_breakers") {
		const basePath = process.cwd();
		await ensureDbOpen(basePath);
		if (!isDbAvailable()) {
			ctx.ui.notify("Database unavailable", "error");
			return;
		}
		const ids = getDistinctGateIds();
		if (ids.length === 0) {
			ctx.ui.notify(
				"No gates have run yet — no circuit breakers to display.",
				"info",
			);
			return;
		}
		const lines = ["Circuit breakers", ""];
		for (const id of ids) {
			const cb = getGateCircuitBreaker(id);
			const icon =
				cb.state === "open" ? "🔴" : cb.state === "half-open" ? "🟡" : "🟢";
			lines.push(
				`${icon} ${id}: ${cb.state} | streak=${cb.failureStreak} | lastFail=${cb.lastFailureAt ?? "never"} | opened=${cb.openedAt ?? "never"}`,
			);
		}
		ctx.ui.notify(lines.join("\n"), "info");
		return;
	}
	if (trimmed === "gates") {
		const basePath = process.cwd();
		await ensureDbOpen(basePath);
		if (!isDbAvailable()) {
			ctx.ui.notify("Database unavailable", "error");
			return;
		}
		const gateIds = getDistinctGateIds();
		if (gateIds.length === 0) {
			ctx.ui.notify("No UOK gate runs recorded yet.", "info");
			return;
		}
		const lines = ["Observed gates", ""];
		for (const id of gateIds) {
			const cb = getGateCircuitBreaker(id);
			lines.push(
				`  ${id}  cb=${cb.state} streak=${cb.failureStreak} lastFail=${cb.lastFailureAt ?? "never"}`,
			);
		}
		ctx.ui.notify(lines.join("\n"), "info");
		return;
	}
	if (trimmed === "messages" || trimmed.startsWith("messages ")) {
		const basePath = process.cwd();
		await ensureDbOpen(basePath);
		if (!isDbAvailable()) {
			ctx.ui.notify("Database unavailable", "error");
			return;
		}
		const sub = trimmed.slice("messages".length).trim();
		if (sub === "compact") {
			const bus = new MessageBus(basePath);
			const result = bus.compact();
			ctx.ui.notify(
				`Message bus compacted: ${result.before} → ${result.after} messages`,
				"info",
			);
			return;
		}
		const m = getUokMessageBusMetrics();
		const lines = ["Message bus", ""];
		lines.push(`Total messages: ${m.totalMessages}`);
		lines.push(`Unread messages: ${m.totalUnread}`);
		lines.push(`Unique agents: ${m.uniqueAgents}`);
		lines.push(`Unique conversations: ${m.uniqueConversations}`);
		lines.push("");
		lines.push(
			"Tip: /uok messages compact — remove messages older than retention period",
		);
		ctx.ui.notify(lines.join("\n"), "info");
		return;
	}
	const status = await collectUokStatus(process.cwd());
	if (trimmed === "--json" || trimmed === "json") {
		ctx.ui.notify(JSON.stringify(status, null, 2), "info");
		return;
	}
	ctx.ui.notify(
		formatUokStatus(status),
		status.startupBlocked ? "warning" : "info",
	);
}
