import { EventEmitter } from 'node:events';
import type { UpstreamConnection, JsonRpcResponse } from './types.js';

export type HealthState = 'healthy' | 'degraded' | 'disconnected';

export interface HealthStatus {
  name: string;
  state: HealthState;
  lastCheck: number;
  consecutiveFailures: number;
}

export interface HealthMonitorOptions {
  /** Interval between health checks in ms (default: 30000) */
  intervalMs?: number;
  /** Number of failures before marking disconnected (default: 3) */
  failureThreshold?: number;
}

/**
 * Monitors upstream connection health with periodic pings.
 * Emits 'change' events when an upstream's health state changes.
 */
export class HealthMonitor extends EventEmitter {
  private statuses = new Map<string, HealthStatus>();
  private upstreams = new Map<string, UpstreamConnection>();
  private timer: ReturnType<typeof setInterval> | null = null;
  private readonly intervalMs: number;
  private readonly failureThreshold: number;

  constructor(opts?: HealthMonitorOptions) {
    super();
    this.intervalMs = opts?.intervalMs ?? 30000;
    this.failureThreshold = opts?.failureThreshold ?? 3;
  }

  track(upstream: UpstreamConnection): void {
    this.upstreams.set(upstream.name, upstream);
    this.statuses.set(upstream.name, {
      name: upstream.name,
      state: upstream.isAlive() ? 'healthy' : 'disconnected',
      lastCheck: Date.now(),
      consecutiveFailures: 0,
    });
  }

  untrack(name: string): void {
    this.upstreams.delete(name);
    this.statuses.delete(name);
  }

  start(): void {
    if (this.timer) return;
    this.timer = setInterval(() => void this.checkAll(), this.intervalMs);
  }

  stop(): void {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  }

  getStatus(name: string): HealthStatus | undefined {
    return this.statuses.get(name);
  }

  getAllStatuses(): HealthStatus[] {
    return [...this.statuses.values()];
  }

  isHealthy(name: string): boolean {
    const status = this.statuses.get(name);
    return status?.state === 'healthy';
  }

  async checkAll(): Promise<void> {
    const checks = [...this.upstreams.entries()].map(([name, upstream]) =>
      this.checkOne(name, upstream),
    );
    await Promise.allSettled(checks);
  }

  private async checkOne(name: string, upstream: UpstreamConnection): Promise<void> {
    const status = this.statuses.get(name);
    if (!status) return;

    const oldState = status.state;

    if (!upstream.isAlive()) {
      status.consecutiveFailures = this.failureThreshold;
      status.state = 'disconnected';
      status.lastCheck = Date.now();
      if (oldState !== 'disconnected') {
        this.emit('change', { name, oldState, newState: 'disconnected' });
      }
      return;
    }

    try {
      const response: JsonRpcResponse = await upstream.send({
        jsonrpc: '2.0',
        id: `health-${name}-${Date.now()}`,
        method: 'ping',
      });

      // A response (even an error for unknown method) means the server is alive
      if (response) {
        status.consecutiveFailures = 0;
        status.state = 'healthy';
      }
    } catch {
      status.consecutiveFailures++;
      if (status.consecutiveFailures >= this.failureThreshold) {
        status.state = 'disconnected';
      } else {
        status.state = 'degraded';
      }
    }

    status.lastCheck = Date.now();
    if (oldState !== status.state) {
      this.emit('change', { name, oldState, newState: status.state });
    }
  }
}
