import type { IMetrics, IBackendMetrics, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
import type { RustProxyBridge } from './rust-proxy-bridge.js';

/**
 * Adapts Rust JSON metrics to the IMetrics interface.
 *
 * Polls the Rust binary periodically via the bridge and caches the result.
 * All IMetrics getters read from the cache synchronously.
 *
 * Rust Metrics JSON fields (camelCase via serde):
 *   activeConnections, totalConnections, bytesIn, bytesOut,
 *   throughputInBytesPerSec, throughputOutBytesPerSec,
 *   routes: { [routeName]: { activeConnections, totalConnections, bytesIn, bytesOut, ... } }
 */
export class RustMetricsAdapter implements IMetrics {
  private bridge: RustProxyBridge;
  private cache: any = null;
  private pollTimer: ReturnType<typeof setInterval> | null = null;
  private pollIntervalMs: number;

  constructor(bridge: RustProxyBridge, pollIntervalMs = 1000) {
    this.bridge = bridge;
    this.pollIntervalMs = pollIntervalMs;
  }

  /**
   * Poll Rust for metrics once. Can be awaited to ensure cache is fresh.
   */
  public async poll(): Promise<void> {
    try {
      this.cache = await this.bridge.getMetrics();
    } catch {
      // Ignore poll errors (bridge may be shutting down)
    }
  }

  public startPolling(): void {
    if (this.pollTimer) return;
    // Immediate first poll so cache is populated ASAP
    this.poll();
    this.pollTimer = setInterval(() => {
      this.poll();
    }, this.pollIntervalMs);
    if (this.pollTimer.unref) {
      this.pollTimer.unref();
    }
  }

  public stopPolling(): void {
    if (this.pollTimer) {
      clearInterval(this.pollTimer);
      this.pollTimer = null;
    }
  }

  // --- IMetrics implementation ---

  public connections = {
    active: (): number => {
      return this.cache?.activeConnections ?? 0;
    },
    total: (): number => {
      return this.cache?.totalConnections ?? 0;
    },
    byRoute: (): Map<string, number> => {
      const result = new Map<string, number>();
      if (this.cache?.routes) {
        for (const [name, rm] of Object.entries(this.cache.routes)) {
          result.set(name, (rm as any).activeConnections ?? 0);
        }
      }
      return result;
    },
    byIP: (): Map<string, number> => {
      const result = new Map<string, number>();
      if (this.cache?.ips) {
        for (const [ip, im] of Object.entries(this.cache.ips)) {
          result.set(ip, (im as any).activeConnections ?? 0);
        }
      }
      return result;
    },
    topIPs: (limit: number = 10): Array<{ ip: string; count: number }> => {
      const result: Array<{ ip: string; count: number }> = [];
      if (this.cache?.ips) {
        for (const [ip, im] of Object.entries(this.cache.ips)) {
          result.push({ ip, count: (im as any).activeConnections ?? 0 });
        }
      }
      result.sort((a, b) => b.count - a.count);
      return result.slice(0, limit);
    },
  };

  public throughput = {
    instant: (): IThroughputData => {
      return {
        in: this.cache?.throughputInBytesPerSec ?? 0,
        out: this.cache?.throughputOutBytesPerSec ?? 0,
      };
    },
    recent: (): IThroughputData => {
      return {
        in: this.cache?.throughputRecentInBytesPerSec ?? 0,
        out: this.cache?.throughputRecentOutBytesPerSec ?? 0,
      };
    },
    average: (): IThroughputData => {
      return this.throughput.instant();
    },
    custom: (_seconds: number): IThroughputData => {
      return this.throughput.instant();
    },
    history: (seconds: number): Array<IThroughputHistoryPoint> => {
      if (!this.cache?.throughputHistory) return [];
      return this.cache.throughputHistory.slice(-seconds).map((p: any) => ({
        timestamp: p.timestampMs,
        in: p.bytesIn,
        out: p.bytesOut,
      }));
    },
    byRoute: (_windowSeconds?: number): Map<string, IThroughputData> => {
      const result = new Map<string, IThroughputData>();
      if (this.cache?.routes) {
        for (const [name, rm] of Object.entries(this.cache.routes)) {
          result.set(name, {
            in: (rm as any).throughputInBytesPerSec ?? 0,
            out: (rm as any).throughputOutBytesPerSec ?? 0,
          });
        }
      }
      return result;
    },
    byIP: (_windowSeconds?: number): Map<string, IThroughputData> => {
      const result = new Map<string, IThroughputData>();
      if (this.cache?.ips) {
        for (const [ip, im] of Object.entries(this.cache.ips)) {
          result.set(ip, {
            in: (im as any).throughputInBytesPerSec ?? 0,
            out: (im as any).throughputOutBytesPerSec ?? 0,
          });
        }
      }
      return result;
    },
  };

  public requests = {
    perSecond: (): number => {
      return this.cache?.httpRequestsPerSec ?? 0;
    },
    perMinute: (): number => {
      return (this.cache?.httpRequestsPerSecRecent ?? 0) * 60;
    },
    total: (): number => {
      return this.cache?.totalHttpRequests ?? this.cache?.totalConnections ?? 0;
    },
  };

  public totals = {
    bytesIn: (): number => {
      return this.cache?.bytesIn ?? 0;
    },
    bytesOut: (): number => {
      return this.cache?.bytesOut ?? 0;
    },
    connections: (): number => {
      return this.cache?.totalConnections ?? 0;
    },
  };

  public backends = {
    byBackend: (): Map<string, IBackendMetrics> => {
      const result = new Map<string, IBackendMetrics>();
      if (this.cache?.backends) {
        for (const [key, bm] of Object.entries(this.cache.backends)) {
          const m = bm as any;
          const totalTimeUs = m.totalConnectTimeUs ?? 0;
          const count = m.connectCount ?? 0;
          const poolHits = m.poolHits ?? 0;
          const poolMisses = m.poolMisses ?? 0;
          const poolTotal = poolHits + poolMisses;
          result.set(key, {
            protocol: m.protocol ?? 'unknown',
            activeConnections: m.activeConnections ?? 0,
            totalConnections: m.totalConnections ?? 0,
            connectErrors: m.connectErrors ?? 0,
            handshakeErrors: m.handshakeErrors ?? 0,
            requestErrors: m.requestErrors ?? 0,
            avgConnectTimeMs: count > 0 ? (totalTimeUs / count) / 1000 : 0,
            poolHitRate: poolTotal > 0 ? poolHits / poolTotal : 0,
            h2Failures: m.h2Failures ?? 0,
          });
        }
      }
      return result;
    },
    protocols: (): Map<string, string> => {
      const result = new Map<string, string>();
      if (this.cache?.backends) {
        for (const [key, bm] of Object.entries(this.cache.backends)) {
          result.set(key, (bm as any).protocol ?? 'unknown');
        }
      }
      return result;
    },
    topByErrors: (limit: number = 10): Array<{ backend: string; errors: number }> => {
      const result: Array<{ backend: string; errors: number }> = [];
      if (this.cache?.backends) {
        for (const [key, bm] of Object.entries(this.cache.backends)) {
          const m = bm as any;
          const errors = (m.connectErrors ?? 0) + (m.handshakeErrors ?? 0) + (m.requestErrors ?? 0);
          if (errors > 0) result.push({ backend: key, errors });
        }
      }
      result.sort((a, b) => b.errors - a.errors);
      return result.slice(0, limit);
    },
  };

  public udp = {
    activeSessions: (): number => this.cache?.activeUdpSessions ?? 0,
    totalSessions: (): number => this.cache?.totalUdpSessions ?? 0,
    datagramsIn: (): number => this.cache?.totalDatagramsIn ?? 0,
    datagramsOut: (): number => this.cache?.totalDatagramsOut ?? 0,
  };

  public percentiles = {
    connectionDuration: (): { p50: number; p95: number; p99: number } => {
      return { p50: 0, p95: 0, p99: 0 };
    },
    bytesTransferred: (): {
      in: { p50: number; p95: number; p99: number };
      out: { p50: number; p95: number; p99: number };
    } => {
      return {
        in: { p50: 0, p95: 0, p99: 0 },
        out: { p50: 0, p95: 0, p99: 0 },
      };
    },
  };
}
