/**
 * UOK Prometheus-style Metrics Exposition
 *
 * Purpose: expose gate-run counters, circuit breaker states, and latency
 * histograms in Prometheus text format so external collectors (or a local
 * dashboard) can scrape them without needing SQLite access.
 *
 * Consumer: health widgets, /uok status, and external monitoring.
 */

import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { sfRoot } from "../paths.js";
import {
	getDistinctGateIds,
	getGateCircuitBreaker,
	getGateLatencyStats,
	getGateRunStats,
	getUokMessageBusMetrics,
	isDbAvailable,
} from "../sf-db.js";

const DEFAULT_GATE_NAMES = [
	"security-guard",
	"cost-guard",
	"outcome-learning",
	"multi-package-healing",
	"chaos-monkey",
	"verification-gate",
	"post-execution-checks",
	"milestone-validation-post-check",
];

const METRICS_CACHE_TTL_MS = 30_000;
let _metricsCacheText = null;
let _metricsCacheKey = null;
let _metricsCacheTs = 0;

function fmtCounter(name, value, labels = {}) {
	const labelStr = Object.entries(labels)
		.map(([k, v]) => `${k}="${v}"`)
		.join(",");
	const suffix = labelStr ? `{${labelStr}}` : "";
	return `${name}${suffix} ${value}`;
}

function fmtGauge(name, value, labels = {}) {
	return fmtCounter(name, value, labels);
}

function collectGateMetrics(gateIds) {
	const lines = [];
	for (const gateId of gateIds) {
		const stats = getGateRunStats(gateId, 24);
		lines.push(
			fmtCounter("uok_gate_runs_total", stats.total, { gate_id: gateId }),
		);
		lines.push(
			fmtCounter("uok_gate_runs_passed_total", stats.pass, { gate_id: gateId }),
		);
		lines.push(
			fmtCounter("uok_gate_runs_failed_total", stats.fail, { gate_id: gateId }),
		);
		lines.push(
			fmtCounter("uok_gate_runs_retry_total", stats.retry, { gate_id: gateId }),
		);

		const latency = getGateLatencyStats(gateId, 24);
		lines.push(
			fmtGauge("uok_gate_latency_avg_ms", latency.avgMs, { gate_id: gateId }),
		);
		lines.push(
			fmtGauge("uok_gate_latency_p50_ms", latency.p50Ms, { gate_id: gateId }),
		);
		lines.push(
			fmtGauge("uok_gate_latency_p95_ms", latency.p95Ms, { gate_id: gateId }),
		);
		lines.push(
			fmtGauge("uok_gate_latency_max_ms", latency.maxMs, { gate_id: gateId }),
		);

		const breaker = getGateCircuitBreaker(gateId);
		const stateMap = { closed: 0, "half-open": 1, open: 2 };
		lines.push(
			fmtGauge("uok_gate_circuit_breaker_state", stateMap[breaker.state] ?? 0, {
				gate_id: gateId,
				state: breaker.state,
			}),
		);
		lines.push(
			fmtGauge(
				"uok_gate_circuit_breaker_failure_streak",
				breaker.failureStreak,
				{
					gate_id: gateId,
				},
			),
		);
	}
	return lines;
}

function collectMessageBusMetrics() {
	const m = getUokMessageBusMetrics();
	const lines = [];
	lines.push(fmtGauge("uok_message_bus_total_messages", m.totalMessages));
	lines.push(fmtGauge("uok_message_bus_unread_messages", m.totalUnread));
	lines.push(fmtGauge("uok_message_bus_unique_agents", m.uniqueAgents));
	lines.push(
		fmtGauge("uok_message_bus_unique_conversations", m.uniqueConversations),
	);
	return lines;
}

export function buildMetricsText(gateIds) {
	const cacheKey = gateIds ? gateIds.join(",") : "";
	const now = Date.now();
	if (
		_metricsCacheText &&
		_metricsCacheKey === cacheKey &&
		now - _metricsCacheTs < METRICS_CACHE_TTL_MS
	) {
		return _metricsCacheText;
	}
	const lines = [
		"# HELP uok_gate_runs_total Total gate runs in the last 24h",
		"# TYPE uok_gate_runs_total counter",
		"# HELP uok_gate_runs_passed_total Passed gate runs in the last 24h",
		"# TYPE uok_gate_runs_passed_total counter",
		"# HELP uok_gate_runs_failed_total Failed gate runs in the last 24h",
		"# TYPE uok_gate_runs_failed_total counter",
		"# HELP uok_gate_runs_retry_total Retried gate runs in the last 24h",
		"# TYPE uok_gate_runs_retry_total counter",
		"# HELP uok_gate_latency_avg_ms Average gate execution latency in ms",
		"# TYPE uok_gate_latency_avg_ms gauge",
		"# HELP uok_gate_latency_p50_ms P50 gate execution latency in ms",
		"# TYPE uok_gate_latency_p50_ms gauge",
		"# HELP uok_gate_latency_p95_ms P95 gate execution latency in ms",
		"# TYPE uok_gate_latency_p95_ms gauge",
		"# HELP uok_gate_latency_max_ms Max gate execution latency in ms",
		"# TYPE uok_gate_latency_max_ms gauge",
		"# HELP uok_gate_circuit_breaker_state Circuit breaker state (0=closed, 1=half-open, 2=open)",
		"# TYPE uok_gate_circuit_breaker_state gauge",
		"# HELP uok_gate_circuit_breaker_failure_streak Consecutive failures since last pass",
		"# TYPE uok_gate_circuit_breaker_failure_streak gauge",
		"# HELP uok_message_bus_total_messages Total messages in the bus",
		"# TYPE uok_message_bus_total_messages gauge",
		"# HELP uok_message_bus_unread_messages Unread messages in the bus",
		"# TYPE uok_message_bus_unread_messages gauge",
		"# HELP uok_message_bus_unique_agents Unique agent recipients",
		"# TYPE uok_message_bus_unique_agents gauge",
		"# HELP uok_message_bus_unique_conversations Unique agent conversation pairs",
		"# TYPE uok_message_bus_unique_conversations gauge",
	];
	if (isDbAvailable()) {
		const ids =
			gateIds && gateIds.length > 0
				? gateIds
				: getDistinctGateIds().length > 0
					? getDistinctGateIds()
					: DEFAULT_GATE_NAMES;
		lines.push(...collectGateMetrics(ids));
		lines.push(...collectMessageBusMetrics());
	}
	const text = lines.join("\n") + "\n";
	_metricsCacheText = text;
	_metricsCacheKey = cacheKey;
	_metricsCacheTs = now;
	return text;
}

export function invalidateMetricsCache() {
	_metricsCacheText = null;
	_metricsCacheKey = null;
	_metricsCacheTs = 0;
}

export function metricsPath(basePath) {
	return join(sfRoot(basePath), "runtime", "uok-metrics.prom");
}

export function writeUokMetrics(basePath, gateIds) {
	const path = metricsPath(basePath);
	const dir = join(sfRoot(basePath), "runtime");
	mkdirSync(dir, { recursive: true });
	writeFileSync(path, buildMetricsText(gateIds), "utf-8");
	return path;
}

export function readUokMetrics(basePath) {
	const path = metricsPath(basePath);
	if (!existsSync(path)) return null;
	try {
		return readFileSync(path, "utf-8");
	} catch {
		return null;
	}
}
