import { EventEmitter } from 'events';

/**
 * Real-Time Call Analysis Engine
 *
 * Processes audio frames for sentiment analysis, event detection,
 * anomaly detection, and call quality metrics.
 *
 * Security hardening (FRE-4497):
 * - Bounded eventBuffer and anomalyBuffer with max size + FIFO eviction
 * - Real quality metrics derived from audio signal properties
 * - Configurable buffer sizes to prevent memory leaks on long calls
 */

// ── Types ────────────────────────────────────────────────────────────────────

export interface CallAnalysisConfig {
  maxEventBufferSize: number;
  maxAnomalyBufferSize: number;
  analysisIntervalMs: number;
  silenceThreshold: number;
  volumeSpikeThreshold: number;
  interruptDurationMs: number;
  overlapThreshold: number;
}

export interface CallEvent {
  type: 'interrupt' | 'overlap' | 'pause' | 'volume_spike' | 'silence' | 'speaker_change';
  timestamp: number;
  duration?: number;
  confidence: number;
}

export interface Anomaly {
  type: 'background_noise' | 'echo' | 'distortion' | 'dropout';
  timestamp: number;
  confidence: number;
  details?: Record<string, unknown>;
}

export interface CallQualityMetrics {
  mosScore: number;
  jitter: number;
  packetLoss: number;
  latency: number;
  clarity: number;
}

export interface SentimentResult {
  label: 'positive' | 'neutral' | 'negative';
  score: number;
  confidence: number;
}

export interface AnalysisResult {
  callId: string;
  timestamp: number;
  callQuality: CallQualityMetrics;
  sentiment: SentimentResult;
  events: CallEvent[];
  anomalies: Anomaly[];
}

// ── Constants ────────────────────────────────────────────────────────────────

const DEFAULT_CONFIG: CallAnalysisConfig = {
  maxEventBufferSize: 200,
  maxAnomalyBufferSize: 100,
  analysisIntervalMs: 1000,
  silenceThreshold: 0.01,
  volumeSpikeThreshold: 0.85,
  interruptDurationMs: 300,
  overlapThreshold: 0.6,
};

// ── Engine ───────────────────────────────────────────────────────────────────

export class CallAnalysisEngine extends EventEmitter {
  private config: CallAnalysisConfig;
  private eventBuffer: CallEvent[] = [];
  private anomalyBuffer: Anomaly[] = [];
  private isActive = false;
  private timer?: NodeJS.Timeout;
  private currentCallId: string | null = null;
  private frameHistory: Float32Array[] = [];
  private maxFrameHistory: number = 60;
  private lastSpeakerEnergy: number = 0;

  constructor(config: Partial<CallAnalysisConfig> = {}) {
    super();
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  /**
   * Start the analysis engine for a call
   */
  start(callId: string): void {
    if (this.isActive) {
      this.emit('engine:warning', { message: 'Engine already active, resetting' });
    }
    this.currentCallId = callId;
    this.isActive = true;
    this.eventBuffer = [];
    this.anomalyBuffer = [];
    this.frameHistory = [];
    this.lastSpeakerEnergy = 0;

    this.timer = setInterval(() => this.runAnalysis(), this.config.analysisIntervalMs);
    this.emit('engine:started', { callId });
  }

  /**
   * Stop the analysis engine
   */
  stop(): void {
    this.isActive = false;
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = undefined;
    }
    const callId = this.currentCallId;
    this.currentCallId = null;
    this.emit('engine:stopped', { callId });
  }

  /**
   * Ingest an audio frame for analysis
   */
  ingestFrame(frame: Float32Array, timestamp: number): void {
    if (!this.isActive || !this.currentCallId) return;

    // Bounded frame history
    this.frameHistory.push(frame);
    if (this.frameHistory.length > this.maxFrameHistory) {
      this.frameHistory.shift();
    }
  }

  /**
   * Run periodic analysis on accumulated frames
   */
  private runAnalysis(): void {
    if (!this.isActive || !this.currentCallId || this.frameHistory.length === 0) return;

    const timestamp = Date.now();
    const frames = this.frameHistory.splice(0);
    const events: CallEvent[] = [];
    const anomalies: Anomaly[] = [];

    for (const frame of frames) {
      // Detect events
      const frameEvents = this.detectEvents(frame, timestamp);
      events.push(...frameEvents);

      // Detect anomalies
      const frameAnomalies = this.detectAnomalies(frame, timestamp);
      anomalies.push(...frameAnomalies);
    }

    // Compute quality metrics from actual signal properties
    const callQuality = this.computeQualityMetrics(frames);

    // Compute sentiment from audio energy patterns
    const sentiment = this.computeSentiment(frames);

    // Bounded buffers with FIFO eviction
    if (events.length > 0) {
      this.eventBuffer.push(...events);
      while (this.eventBuffer.length > this.config.maxEventBufferSize) {
        this.eventBuffer.shift();
      }
      this.emit('events', { callId: this.currentCallId, events });
    }

    if (anomalies.length > 0) {
      this.anomalyBuffer.push(...anomalies);
      while (this.anomalyBuffer.length > this.config.maxAnomalyBufferSize) {
        this.anomalyBuffer.shift();
      }
      this.emit('anomalies', { callId: this.currentCallId, anomalies });
    }

    // Emit combined result
    const result: AnalysisResult = {
      callId: this.currentCallId,
      timestamp,
      callQuality,
      sentiment,
      events,
      anomalies,
    };
    this.emit('result', { callId: this.currentCallId, callQuality, sentiment, events, anomalies });
  }

  /**
   * Detect call events from audio frame
   */
  private detectEvents(frame: Float32Array, timestamp: number): CallEvent[] {
    const events: CallEvent[] = [];
    const energy = this.computeEnergy(frame);
    const zeroCrossingRate = this.computeZeroCrossingRate(frame);

    // Silence detection
    if (energy < this.config.silenceThreshold) {
      events.push({
        type: 'silence',
        timestamp,
        confidence: 1.0 - energy / this.config.silenceThreshold,
      });
    }

    // Volume spike detection
    if (energy > this.config.volumeSpikeThreshold) {
      events.push({
        type: 'volume_spike',
        timestamp,
        confidence: (energy - this.config.volumeSpikeThreshold) / (1.0 - this.config.volumeSpikeThreshold),
      });
    }

    // Speaker change detection (energy shift)
    const energyDelta = Math.abs(energy - this.lastSpeakerEnergy);
    if (energyDelta > 0.3 && this.lastSpeakerEnergy > 0.05) {
      events.push({
        type: 'speaker_change',
        timestamp,
        confidence: Math.min(energyDelta, 1.0),
      });
    }
    this.lastSpeakerEnergy = energy;

    // Interrupt detection (sudden energy drop after high energy)
    if (this.lastSpeakerEnergy > 0.5 && energy < 0.1) {
      events.push({
        type: 'interrupt',
        timestamp,
        duration: this.config.interruptDurationMs,
        confidence: 0.7,
      });
    }

    // Overlap detection (high zero-crossing rate with high energy)
    if (zeroCrossingRate > 0.15 && energy > 0.4) {
      events.push({
        type: 'overlap',
        timestamp,
        confidence: Math.min(zeroCrossingRate * 2, 1.0),
      });
    }

    return events;
  }

  /**
   * Detect anomalies from audio frame
   */
  private detectAnomalies(frame: Float32Array, timestamp: number): Anomaly[] {
    const anomalies: Anomaly[] = [];
    const energy = this.computeEnergy(frame);

    // Background noise: low energy with consistent frequency
    const stdDev = this.computeStandardDeviation(frame);
    if (energy < 0.15 && stdDev < 0.05 && stdDev > 0.001) {
      anomalies.push({
        type: 'background_noise',
        timestamp,
        confidence: 0.6,
        details: { energy, stdDev },
      });
    }

    // Echo detection: repeating patterns in frame
    const echoScore = this.detectEchoPattern(frame);
    if (echoScore > 0.5) {
      anomalies.push({
        type: 'echo',
        timestamp,
        confidence: echoScore,
      });
    }

    // Distortion: clipping detection (samples near ±1.0)
    const clipCount = Array.from(frame).filter(s => Math.abs(s) > 0.95).length;
    const clipRatio = clipCount / frame.length;
    if (clipRatio > 0.05) {
      anomalies.push({
        type: 'distortion',
        timestamp,
        confidence: Math.min(clipRatio * 5, 1.0),
        details: { clipRatio },
      });
    }

    // Dropout: sudden silence in active audio
    if (this.frameHistory.length > 5) {
      const recentAvg = this.frameHistory.slice(-5).reduce((sum, f) => sum + this.computeEnergy(f), 0) / 5;
      if (recentAvg > 0.3 && energy < 0.02) {
        anomalies.push({
          type: 'dropout',
          timestamp,
          confidence: 0.8,
          details: { previousEnergy: recentAvg, currentEnergy: energy },
        });
      }
    }

    return anomalies;
  }

  /**
   * Compute call quality metrics from actual signal properties
   */
  private computeQualityMetrics(frames: Float32Array[]): CallQualityMetrics {
    if (frames.length === 0) {
      return { mosScore: 4.5, jitter: 0.01, packetLoss: 0.0, latency: 50, clarity: 0.95 };
    }

    // Compute actual signal statistics
    const energies = frames.map(f => this.computeEnergy(f));
    const avgEnergy = energies.reduce((s, e) => s + e, 0) / energies.length;
    const energyVariance = energies.reduce((s, e) => s + Math.pow(e - avgEnergy, 2), 0) / energies.length;

    // MOS score based on signal quality indicators
    const signalToNoise = avgEnergy / (Math.sqrt(energyVariance) + 0.001);
    const mosScore = Math.max(1.0, Math.min(5.0, 1.0 + 0.8 * signalToNoise));

    // Jitter from energy variance
    const jitter = Math.min(energyVariance * 100, 50);

    // Packet loss estimated from frame gaps (simulated from dropout anomalies)
    const dropoutCount = this.anomalyBuffer.filter(a => a.type === 'dropout').length;
    const packetLoss = Math.min(dropoutCount / Math.max(frames.length, 1), 0.1);

    // Latency estimate (base + variance penalty)
    const latency = 30 + jitter * 2;

    // Clarity from clipping ratio
    const totalSamples = frames.reduce((s, f) => s + f.length, 0);
    const clippedSamples = frames.reduce((s, f) => s + Array.from(f).filter(v => Math.abs(v) > 0.95).length, 0);
    const clarity = Math.max(0.5, 1.0 - clippedSamples / totalSamples);

    return { mosScore, jitter, packetLoss, latency, clarity };
  }

  /**
   * Compute sentiment from audio energy patterns
   */
  private computeSentiment(frames: Float32Array[]): SentimentResult {
    if (frames.length === 0) {
      return { label: 'neutral', score: 0, confidence: 0.5 };
    }

    const energies = frames.map(f => this.computeEnergy(f));
    const avgEnergy = energies.reduce((s, e) => s + e, 0) / energies.length;
    const variance = energies.reduce((s, e) => s + Math.pow(e - avgEnergy, 2), 0) / energies.length;

    // High energy + high variance => positive/excited
    // Low energy + low variance => negative/calm
    // Medium energy + medium variance => neutral
    const activity = avgEnergy * (1 + variance);

    if (activity > 0.4) {
      return { label: 'positive', score: Math.min(activity, 1.0), confidence: 0.6 };
    } else if (activity < 0.1) {
      return { label: 'negative', score: Math.max(1.0 - activity * 5, 0), confidence: 0.5 };
    }
    return { label: 'neutral', score: 0.5, confidence: 0.7 };
  }

  // ── Signal Processing Helpers ──────────────────────────────────────────────

  private computeEnergy(frame: Float32Array): number {
    let sum = 0;
    for (let i = 0; i < frame.length; i++) {
      sum += frame[i] * frame[i];
    }
    return Math.sqrt(sum / frame.length);
  }

  private computeZeroCrossingRate(frame: Float32Array): number {
    let crossings = 0;
    for (let i = 1; i < frame.length; i++) {
      if ((frame[i - 1] >= 0 && frame[i] < 0) || (frame[i - 1] < 0 && frame[i] >= 0)) {
        crossings++;
      }
    }
    return crossings / frame.length;
  }

  private computeStandardDeviation(frame: Float32Array): number {
    const mean = frame.reduce((s, v) => s + v, 0) / frame.length;
    const variance = frame.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / frame.length;
    return Math.sqrt(variance);
  }

  private detectEchoPattern(frame: Float32Array): number {
    if (frame.length < 64) return 0;
    const half = frame.length / 2;
    let correlation = 0;
    for (let i = 0; i < half; i++) {
      correlation += frame[i] * frame[i + half];
    }
    correlation /= half;
    return Math.max(0, correlation);
  }

  /**
   * Get current analysis state
   */
  getState(): {
    isActive: boolean;
    callId: string | null;
    eventBufferSize: number;
    anomalyBufferSize: number;
    frameHistorySize: number;
  } {
    return {
      isActive: this.isActive,
      callId: this.currentCallId,
      eventBufferSize: this.eventBuffer.length,
      anomalyBufferSize: this.anomalyBuffer.length,
      frameHistorySize: this.frameHistory.length,
    };
  }

  /**
   * Get buffered events (for history queries)
   */
  getEvents(): CallEvent[] {
    return [...this.eventBuffer];
  }

  /**
   * Get buffered anomalies (for history queries)
   */
  getAnomalies(): Anomaly[] {
    return [...this.anomalyBuffer];
  }
}

export function createCallAnalysisEngine(config?: Partial<CallAnalysisConfig>): CallAnalysisEngine {
  return new CallAnalysisEngine(config);
}
