/**
 * HealthMonitor — background health check service for the HA cluster.
 *
 * Runs every 10s and checks:
 *   1. Write pool connectivity (primary DB)
 *   2. Read pool connectivity (replicas)
 *   3. Patroni cluster status via REST API (optional, when HA mode)
 *
 * On failure: records to circuit breaker (DbRouter), emits audit event to MongoDB.
 * On recovery: emits recovery event.
 *
 * Phase 3c (PLANS.md)
 */

import { DbRouter } from './dbRouter.js';
import { LoggingService } from '#services/file-system/logService';
import { createAuditStrategy } from '#services/audit/strategies/auditStrategyFactoryService.js';

const logger = new LoggingService();
const auditStrategy = createAuditStrategy(process.env.AUDIT_MODE ?? 'direct');

export interface ClusterNodeHealth {
	name: string;
	host: string;
	role: 'master' | 'replica' | 'unknown';
	healthy: boolean;
	lagMb?: number;
}

export interface ClusterHealthStatus {
	ok: boolean;
	writePoolHealthy: boolean;
	readPoolHealthy: boolean;
	nodes: ClusterNodeHealth[];
	checkedAt: Date;
}

export class HealthMonitor {
	private static intervalId: ReturnType<typeof setInterval> | null = null;
	private static running = false;
	private static lastStatus: ClusterHealthStatus | null = null;

	/**
	 * Start the health monitor. Runs indefinitely every `intervalMs` milliseconds.
	 * Idempotent — calling start() twice is a no-op.
	 *
	 * @param intervalMs Polling interval in milliseconds (default: 10_000 = 10s)
	 */
	static start(intervalMs = 10_000): void {
		if (this.running) {
			void logger.logAsync('warn', 'HealthMonitor', 'Already running, ignoring start() call', null);
			return;
		}

		this.running = true;
		void logger.logAsync('info', 'HealthMonitor', `Starting health monitor (interval: ${intervalMs}ms)`, null);

		// Run immediately, then schedule
		void this.check();

		this.intervalId = setInterval(() => {
			void this.check();
		}, intervalMs);
	}

	/**
	 * Stop the health monitor.
	 */
	static stop(): void {
		if (this.intervalId !== null) {
			clearInterval(this.intervalId);
			this.intervalId = null;
		}
		this.running = false;
		void logger.logAsync('info', 'HealthMonitor', 'Health monitor stopped', null);
	}

	/**
	 * Run a single health check pass.
	 */
	static async check(): Promise<ClusterHealthStatus> {
		const prevStatus = this.lastStatus;

		// Check both pools via DbRouter
		const [writePool, readPool] = await Promise.all([
			this.checkPool('write'),
			this.checkPool('read'),
		]);

		const status: ClusterHealthStatus = {
			ok: writePool.healthy && readPool.healthy,
			writePoolHealthy: writePool.healthy,
			readPoolHealthy: readPool.healthy,
			nodes: [],
			checkedAt: new Date(),
		};

		this.lastStatus = status;

		// Detect state transitions (healthy → degraded or degraded → healthy)
		if (prevStatus) {
			// Write pool degraded transition
			if (prevStatus.writePoolHealthy && !status.writePoolHealthy) {
				void this.emitEvent('WRITE_POOL_DEGRADED', {
					host: writePool.host,
					error: writePool.error,
				});
			} else if (!prevStatus.writePoolHealthy && status.writePoolHealthy) {
				void this.emitEvent('WRITE_POOL_RECOVERED', { host: writePool.host });
			}

			// Read pool degraded transition
			if (prevStatus.readPoolHealthy && !status.readPoolHealthy) {
				void this.emitEvent('READ_POOL_DEGRADED', {
					host: readPool.host,
					error: readPool.error,
				});
			} else if (!prevStatus.readPoolHealthy && status.readPoolHealthy) {
				void this.emitEvent('READ_POOL_RECOVERED', { host: readPool.host });
			}

			// Full cluster degraded transition
			if (prevStatus.ok && !status.ok) {
				void this.emitEvent('CLUSTER_DEGRADED', {
					writeHealthy: status.writePoolHealthy,
					readHealthy: status.readPoolHealthy,
				});
			} else if (!prevStatus.ok && status.ok) {
				void this.emitEvent('CLUSTER_RECOVERED', {});
			}
		}

		return status;
	}

	private static async checkPool(
		poolName: 'write' | 'read',
	): Promise<{ healthy: boolean; host: string; error?: string }> {
		try {
			const status = DbRouter.getStatus();
			const poolStatus = status[poolName];

			if (poolStatus.degraded) {
				return {
					healthy: false,
					host: poolName === 'write'
						? (process.env.PG_WRITER_HOST ?? 'localhost')
						: (process.env.PG_READER_HOST ?? 'localhost'),
					error: `Circuit breaker degraded (${poolStatus.consecutiveFailures} failures, last: ${new Date(poolStatus.lastFailure).toISOString()})`,
				};
			}

			// Quick connectivity check — authenticate() opens a connection from the pool
			const result = await DbRouter.healthCheck();
			const healthy = poolName === 'write' ? result.write : result.read;

			return {
				healthy,
				host: poolName === 'write'
					? (process.env.PG_WRITER_HOST ?? 'localhost')
					: (process.env.PG_READER_HOST ?? 'localhost'),
				...(healthy ? {} : { error: 'DB health check failed' }),
			};
		} catch (err) {
			return {
				healthy: false,
				host: poolName === 'write'
					? (process.env.PG_WRITER_HOST ?? 'localhost')
					: (process.env.PG_READER_HOST ?? 'localhost'),
				error: err instanceof Error ? err.message : String(err),
			};
		}
	}

	/**
	 * Emit a system audit event to MongoDB for HA state transitions.
	 */
	private static async emitEvent(
		eventType: string,
		metadata: Record<string, unknown>,
	): Promise<void> {
		void logger.logAsync(
			'warn',
			'HealthMonitor',
			`HA event: ${eventType} — ${JSON.stringify(metadata)}`,
			null,
		);

		try {
			await auditStrategy.logSystemAudit({
				action: eventType,
				module: 'ha_cluster',
				actor_name: 'system',
				actor_id: 'health_monitor',
				severity: eventType.includes('DEGRADED') ? 'HIGH' : 'LOW',
				metadata,
			});
		} catch {
			// Never throw from event emission
		}
	}

	/**
	 * Get the last health check result without running a new check.
	 */
	static getLastStatus(): ClusterHealthStatus | null {
		return this.lastStatus;
	}

	/**
	 * Get current DbRouter pool status (circuit breaker state).
	 */
	static getPoolCircuitBreakerStatus() {
		return DbRouter.getStatus();
	}
}
