import type { IMetrics, IBackendMetrics, IProtocolCacheEntry, IProtocolDistribution, IRequestRateMetrics, IThroughputData, IThroughputHistoryPoint } from './models/metrics-types.js';
import type { RustProxyBridge } from './rust-proxy-bridge.js';
import type { IRustBackendMetrics, IRustHttpDomainRequestMetrics, IRustIpMetrics, IRustMetricsSnapshot, IRustRouteMetrics } from './models/rust-types.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: IRustMetricsSnapshot | null = 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) as Array<[string, IRustRouteMetrics]>) {
          result.set(name, rm.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) as Array<[string, IRustIpMetrics]>) {
          result.set(ip, im.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) as Array<[string, IRustIpMetrics]>) {
          result.push({ ip, count: im.activeConnections ?? 0 });
        }
      }
      result.sort((a, b) => b.count - a.count);
      return result.slice(0, limit);
    },
    domainRequestsByIP: (): Map<string, Map<string, number>> => {
      const result = new Map<string, Map<string, number>>();
      if (this.cache?.ips) {
        for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
          const dr = im.domainRequests;
          if (dr && typeof dr === 'object') {
            const domainMap = new Map<string, number>();
            for (const [domain, count] of Object.entries(dr)) {
              domainMap.set(domain, count as number);
            }
            if (domainMap.size > 0) {
              result.set(ip, domainMap);
            }
          }
        }
      }
      return result;
    },
    topDomainRequests: (limit: number = 20): Array<{ ip: string; domain: string; count: number }> => {
      const result: Array<{ ip: string; domain: string; count: number }> = [];
      if (this.cache?.ips) {
        for (const [ip, im] of Object.entries(this.cache.ips) as Array<[string, IRustIpMetrics]>) {
          const dr = im.domainRequests;
          if (dr && typeof dr === 'object') {
            for (const [domain, count] of Object.entries(dr)) {
              result.push({ ip, domain, count: count as number });
            }
          }
        }
      }
      result.sort((a, b) => b.count - a.count);
      return result.slice(0, limit);
    },
    frontendProtocols: (): IProtocolDistribution => {
      const fp = this.cache?.frontendProtocols;
      return {
        h1Active: fp?.h1Active ?? 0,
        h1Total: fp?.h1Total ?? 0,
        h2Active: fp?.h2Active ?? 0,
        h2Total: fp?.h2Total ?? 0,
        h3Active: fp?.h3Active ?? 0,
        h3Total: fp?.h3Total ?? 0,
        wsActive: fp?.wsActive ?? 0,
        wsTotal: fp?.wsTotal ?? 0,
        otherActive: fp?.otherActive ?? 0,
        otherTotal: fp?.otherTotal ?? 0,
      };
    },
    backendProtocols: (): IProtocolDistribution => {
      const bp = this.cache?.backendProtocols;
      return {
        h1Active: bp?.h1Active ?? 0,
        h1Total: bp?.h1Total ?? 0,
        h2Active: bp?.h2Active ?? 0,
        h2Total: bp?.h2Total ?? 0,
        h3Active: bp?.h3Active ?? 0,
        h3Total: bp?.h3Total ?? 0,
        wsActive: bp?.wsActive ?? 0,
        wsTotal: bp?.wsTotal ?? 0,
        otherActive: bp?.otherActive ?? 0,
        otherTotal: bp?.otherTotal ?? 0,
      };
    },
  };

  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) => ({
        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) as Array<[string, IRustRouteMetrics]>) {
          result.set(name, {
            in: rm.throughputInBytesPerSec ?? 0,
            out: rm.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) as Array<[string, IRustIpMetrics]>) {
          result.set(ip, {
            in: im.throughputInBytesPerSec ?? 0,
            out: im.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;
    },
    byDomain: (): Map<string, IRequestRateMetrics> => {
      const result = new Map<string, IRequestRateMetrics>();
      if (this.cache?.httpDomainRequests) {
        for (const [domain, metrics] of Object.entries(this.cache.httpDomainRequests) as Array<[string, IRustHttpDomainRequestMetrics]>) {
          result.set(domain, {
            perSecond: metrics.requestsPerSecond ?? 0,
            lastMinute: metrics.requestsLastMinute ?? 0,
          });
        }
      }
      return result;
    },
  };

  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) as Array<[string, IRustBackendMetrics]>) {
          const totalTimeUs = bm.totalConnectTimeUs ?? 0;
          const count = bm.connectCount ?? 0;
          const poolHits = bm.poolHits ?? 0;
          const poolMisses = bm.poolMisses ?? 0;
          const poolTotal = poolHits + poolMisses;
          result.set(key, {
            protocol: bm.protocol ?? 'unknown',
            activeConnections: bm.activeConnections ?? 0,
            totalConnections: bm.totalConnections ?? 0,
            connectErrors: bm.connectErrors ?? 0,
            handshakeErrors: bm.handshakeErrors ?? 0,
            requestErrors: bm.requestErrors ?? 0,
            avgConnectTimeMs: count > 0 ? (totalTimeUs / count) / 1000 : 0,
            poolHitRate: poolTotal > 0 ? poolHits / poolTotal : 0,
            h2Failures: bm.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) as Array<[string, IRustBackendMetrics]>) {
          result.set(key, bm.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) as Array<[string, IRustBackendMetrics]>) {
          const errors = (bm.connectErrors ?? 0) + (bm.handshakeErrors ?? 0) + (bm.requestErrors ?? 0);
          if (errors > 0) result.push({ backend: key, errors });
        }
      }
      result.sort((a, b) => b.errors - a.errors);
      return result.slice(0, limit);
    },
    detectedProtocols: (): IProtocolCacheEntry[] => {
      return this.cache?.detectedProtocols ?? [];
    },
  };

  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 },
      };
    },
  };
}
