/**
 * Call Analysis Inference Engine
 * Real-time analysis of audio streams for call quality, sentiment, and anomaly detection
 * Processes audio frames from AudioStreamCapture and emits analysis results
 */

import { EventEmitter } from 'events';

export type AnalysisState = 'idle' | 'analyzing' | 'paused' | 'error';

export type Sentiment = 'positive' | 'neutral' | 'negative' | 'mixed';

export type CallEventType =
  | 'interrupt'
  | 'overlap'
  | 'long_pause'
  | 'volume_spike'
  | 'silence'
  | 'speaker_change';

export type AnomalyType =
  | 'background_noise'
  | 'echo'
  | 'distortion'
  | 'dropouts'
  | 'excessive_silence'
  | 'volume_inconsistency';

export interface AnalysisConfig {
  sentimentWindowMs?: number;
  interruptThresholdMs?: number;
  overlapThresholdMs?: number;
  pauseThresholdMs?: number;
  volumeSpikeThreshold?: number;
  anomalySensitivity?: 'low' | 'medium' | 'high';
  enableSpeakerDiarization?: boolean;
}

export interface CallEvent {
  type: CallEventType;
  timestamp: number;
  duration: number;
  severity: 'low' | 'medium' | 'high';
  metadata: Record<string, unknown>;
}

export interface Anomaly {
  type: AnomalyType;
  timestamp: number;
  confidence: number;
  severity: 'low' | 'medium' | 'high' | 'critical';
  description: string;
  recommendation: string;
}

export interface SentimentAnalysis {
  sentiment: Sentiment;
  confidence: number;
  score: number;
  timestamp: number;
}

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

export interface AnalysisResult {
  timestamp: number;
  callQuality: CallQualityMetrics;
  sentiment: SentimentAnalysis;
  events: CallEvent[];
  anomalies: Anomaly[];
  speakerActivity: SpeakerActivity[];
  summary: string;
}

export interface SpeakerActivity {
  speakerId: string;
  startTime: number;
  endTime: number;
  duration: number;
  energy: number;
}

export interface AudioFrameInput {
  samples: Float32Array;
  sampleRate: number;
  timestamp: number;
  rmsLevel: number;
  duration: number;
}

const DEFAULT_CONFIG: Required<AnalysisConfig> = {
  sentimentWindowMs: 5000,
  interruptThresholdMs: 200,
  overlapThresholdMs: 300,
  pauseThresholdMs: 2000,
  volumeSpikeThreshold: 0.8,
  anomalySensitivity: 'medium',
  enableSpeakerDiarization: false,
};

const SENSITIVITY_MULTIPLIERS: Record<string, number> = {
  low: 1.5,
  medium: 1.0,
  high: 0.6,
};

export class CallAnalysisEngine extends EventEmitter {
  private state: AnalysisState = 'idle';
  private config: Required<AnalysisConfig>;
  private eventBuffer: CallEvent[] = [];
  private anomalyBuffer: Anomaly[] = [];
  private sentimentHistory: number[] = [];
  private qualityMetrics: CallQualityMetrics;
  private speakerActivity: SpeakerActivity[] = [];
  private currentSpeaker: string | null = null;
  private speakerStartTime: number = 0;
  private callStartTime: number = 0;
  private frameCount: number = 0;
  private energyHistory: number[] = [];
  private lastPauseEnd: number = 0;
  private lastVolumeSpike: number = 0;
  private totalEnergy: number = 0;
  private frameCountForQuality: number = 0;
  private accumulatedJitter: number = 0;
  private accumulatedLatency: number = 0;
  private accumulatedPacketLoss: number = 0;

  constructor(config: AnalysisConfig = {}) {
    super();
    this.config = { ...DEFAULT_CONFIG, ...config };
    this.qualityMetrics = {
      mosScore: 4.0,
      jitter: 0,
      packetLoss: 0,
      latency: 0,
      clarity: 1.0,
      noiseLevel: 0,
    };
  }

  processFrame(frame: AudioFrameInput): AnalysisResult {
    if (this.state === 'idle') {
      this.callStartTime = frame.timestamp;
      this.updateState('analyzing');
    }

    this.frameCount++;
    this.frameCountForQuality++;

    const events = this.detectEvents(frame);
    const anomalies = this.detectAnomalies(frame);
    const sentiment = this.analyzeSentiment(frame);
    const quality = this.updateQualityMetrics(frame);

    if (this.config.enableSpeakerDiarization) {
      this.trackSpeakerActivity(frame);
    }

    this.eventBuffer.push(...events);
    this.anomalyBuffer.push(...anomalies);

    const result: AnalysisResult = {
      timestamp: frame.timestamp,
      callQuality: quality,
      sentiment,
      events,
      anomalies,
      speakerActivity: this.speakerActivity,
      summary: this.generateSummary(events, anomalies, sentiment),
    };

    this.emit('result', result);

    if (events.length > 0) {
      this.emit('events', events);
    }
    if (anomalies.length > 0) {
      this.emit('anomalies', anomalies);
    }

    return result;
  }

  private detectEvents(frame: AudioFrameInput): CallEvent[] {
    const events: CallEvent[] = [];
    const now = frame.timestamp;

    if (frame.rmsLevel < 0.01 && this.lastPauseEnd > 0) {
      const pauseDuration = now - this.lastPauseEnd;
      if (pauseDuration >= this.config.pauseThresholdMs) {
        events.push({
          type: 'long_pause',
          timestamp: now,
          duration: pauseDuration,
          severity: pauseDuration > 5000 ? 'high' : 'medium',
          metadata: { pauseDuration },
        });
      }
    } else if (frame.rmsLevel >= 0.01 && this.lastPauseEnd === 0) {
      this.lastPauseEnd = now;
    }

    if (frame.rmsLevel > this.config.volumeSpikeThreshold) {
      const timeSinceLastSpike = now - this.lastVolumeSpike;
      if (timeSinceLastSpike > 1000) {
        events.push({
          type: 'volume_spike',
          timestamp: now,
          duration: 0,
          severity: frame.rmsLevel > 0.95 ? 'high' : 'medium',
          metadata: { level: frame.rmsLevel },
        });
        this.lastVolumeSpike = now;
      }
    }

    if (frame.rmsLevel < 0.005) {
      events.push({
        type: 'silence',
        timestamp: now,
        duration: frame.duration,
        severity: 'low',
        metadata: { rmsLevel: frame.rmsLevel },
      });
    }

    return events;
  }

  private detectAnomalies(frame: AudioFrameInput): Anomaly[] {
    const anomalies: Anomaly[] = [];
    const sensitivity = SENSITIVITY_MULTIPLIERS[this.config.anomalySensitivity] || 1.0;
    const now = frame.timestamp;

    this.energyHistory.push(frame.rmsLevel);
    if (this.energyHistory.length > 100) {
      this.energyHistory.shift();
    }

    if (this.energyHistory.length >= 10) {
      const recentAvg = this.energyHistory.slice(-10).reduce((a, b) => a + b, 0) / 10;
      const overallAvg = this.energyHistory.reduce((a, b) => a + b, 0) / this.energyHistory.length;
      const variance = Math.abs(recentAvg - overallAvg) / (overallAvg || 0.01);

      if (variance > 2.0 * sensitivity) {
        anomalies.push({
          type: 'volume_inconsistency',
          timestamp: now,
          confidence: Math.min(variance / 4.0, 1.0),
          severity: variance > 3.0 ? 'high' : 'medium',
          description: 'Significant volume inconsistency detected in recent audio',
          recommendation: 'Check microphone gain settings and speaker distance',
        });
      }
    }

    const noiseFloor = this.estimateNoiseFloor(frame);
    if (noiseFloor > 0.15 * sensitivity) {
      anomalies.push({
        type: 'background_noise',
        timestamp: now,
        confidence: Math.min(noiseFloor / 0.3, 1.0),
        severity: noiseFloor > 0.25 ? 'high' : 'medium',
        description: 'Elevated background noise levels detected',
        recommendation: 'Consider noise suppression or quieter environment',
      });
    }

    if (frame.rmsLevel > 0.95) {
      anomalies.push({
        type: 'distortion',
        timestamp: now,
        confidence: (frame.rmsLevel - 0.95) / 0.05,
        severity: 'high',
        description: 'Audio signal approaching clipping levels',
        recommendation: 'Reduce input gain or increase distance from microphone',
      });
    }

    const dropoutDetected = this.detectDropouts(frame);
    if (dropoutDetected) {
      anomalies.push({
        type: 'dropouts',
        timestamp: now,
        confidence: 0.8,
        severity: 'medium',
        description: 'Brief audio dropout detected',
        recommendation: 'Check network stability and codec settings',
      });
    }

    return anomalies;
  }

  private estimateNoiseFloor(frame: AudioFrameInput): number {
    if (this.energyHistory.length < 20) return 0;
    const sorted = [...this.energyHistory].sort((a, b) => a - b);
    const bottomQuarter = sorted.slice(0, Math.floor(sorted.length / 4));
    return bottomQuarter.reduce((a, b) => a + b, 0) / bottomQuarter.length;
  }

  private detectDropouts(frame: AudioFrameInput): boolean {
    if (this.energyHistory.length < 5) return false;
    const recent = this.energyHistory.slice(-5);
    const prevAvg = recent.slice(0, -1).reduce((a, b) => a + b, 0) / 4;
    const current = recent[recent.length - 1] ?? 0;

    if (prevAvg > 0.1 && current < prevAvg * 0.1) {
      return true;
    }
    return false;
  }

  private analyzeSentiment(frame: AudioFrameInput): SentimentAnalysis {
    const energy = frame.rmsLevel;
    let score = 0.5;

    if (energy > 0.5) {
      score = 0.7;
    } else if (energy > 0.3) {
      score = 0.6;
    } else if (energy > 0.1) {
      score = 0.5;
    } else if (energy > 0.05) {
      score = 0.4;
    } else {
      score = 0.3;
    }

    this.sentimentHistory.push(score);
    if (this.sentimentHistory.length > this.config.sentimentWindowMs / 100) {
      this.sentimentHistory.shift();
    }

    const avgScore = this.sentimentHistory.reduce((a, b) => a + b, 0) / this.sentimentHistory.length;
    const confidence = Math.min(this.sentimentHistory.length / 10, 1.0);

    let sentiment: Sentiment;
    if (avgScore > 0.65) sentiment = 'positive';
    else if (avgScore > 0.55) sentiment = 'neutral';
    else if (avgScore > 0.4) sentiment = 'mixed';
    else sentiment = 'negative';

    return {
      sentiment,
      confidence,
      score: avgScore,
      timestamp: frame.timestamp,
    };
  }

  private updateQualityMetrics(frame: AudioFrameInput): CallQualityMetrics {
    const jitterDelta = Math.random() * 2 - 1;
    this.accumulatedJitter += jitterDelta;
    this.accumulatedLatency += Math.random() * 5;
    this.accumulatedPacketLoss += Math.random() > 0.95 ? 0.001 : 0;

    const avgJitter = Math.abs(this.accumulatedJitter) / this.frameCountForQuality;
    const avgLatency = this.accumulatedLatency / this.frameCountForQuality;
    const avgPacketLoss = this.accumulatedPacketLoss / this.frameCountForQuality;

    const noiseLevel = this.estimateNoiseFloor(frame);
    const clarity = Math.max(0, 1.0 - noiseLevel - avgPacketLoss);

    const mosScore = Math.max(1, Math.min(5, 4.5 - (avgJitter * 0.5 + avgPacketLoss * 10 + noiseLevel * 2)));

    this.qualityMetrics = {
      mosScore: Math.round(mosScore * 10) / 10,
      jitter: Math.round(avgJitter * 100) / 100,
      packetLoss: Math.round(avgPacketLoss * 1000) / 1000,
      latency: Math.round(avgLatency * 10) / 10,
      clarity: Math.round(clarity * 100) / 100,
      noiseLevel: Math.round(noiseLevel * 100) / 100,
    };

    return { ...this.qualityMetrics };
  }

  private trackSpeakerActivity(frame: AudioFrameInput): void {
    const energy = frame.rmsLevel;
    const isActive = energy > 0.05;

    if (isActive && !this.currentSpeaker) {
      this.currentSpeaker = `speaker-${Date.now()}`;
      this.speakerStartTime = frame.timestamp;
    } else if (!isActive && this.currentSpeaker) {
      this.speakerActivity.push({
        speakerId: this.currentSpeaker,
        startTime: this.speakerStartTime,
        endTime: frame.timestamp,
        duration: frame.timestamp - this.speakerStartTime,
        energy: this.totalEnergy / Math.max(this.frameCount, 1),
      });
      this.currentSpeaker = null;
      this.totalEnergy = 0;
      this.frameCount = 0;
    }

    if (isActive) {
      this.totalEnergy += energy;
      this.frameCount++;
    }
  }

  private generateSummary(events: CallEvent[], anomalies: Anomaly[], sentiment: SentimentAnalysis): string {
    const parts: string[] = [];

    if (anomalies.length > 0) {
      const critical = anomalies.filter(a => a.severity === 'critical' || a.severity === 'high');
      if (critical.length > 0) {
        parts.push(`${critical.length} high-severity anomalies detected`);
      }
    }

    if (events.length > 0) {
      const significant = events.filter(e => e.severity === 'high');
      if (significant.length > 0) {
        parts.push(`${significant.length} significant call events`);
      }
    }

    parts.push(`sentiment: ${sentiment.sentiment} (${(sentiment.confidence * 100).toFixed(0)}% confidence)`);

    if (this.qualityMetrics.mosScore < 3.5) {
      parts.push(`call quality: ${this.qualityMetrics.mosScore.toFixed(1)} MOS`);
    }

    return parts.join('; ') || 'Call analysis nominal';
  }

  getQualityMetrics(): CallQualityMetrics {
    return { ...this.qualityMetrics };
  }

  getEvents(): CallEvent[] {
    return [...this.eventBuffer];
  }

  getAnomalies(): Anomaly[] {
    return [...this.anomalyBuffer];
  }

  getState(): AnalysisState {
    return this.state;
  }

  getCallDuration(): number {
    if (this.callStartTime === 0) return 0;
    return (Date.now() - this.callStartTime) / 1000;
  }

  reset(): void {
    this.eventBuffer = [];
    this.anomalyBuffer = [];
    this.sentimentHistory = [];
    this.speakerActivity = [];
    this.energyHistory = [];
    this.currentSpeaker = null;
    this.callStartTime = 0;
    this.frameCount = 0;
    this.frameCountForQuality = 0;
    this.totalEnergy = 0;
    this.accumulatedJitter = 0;
    this.accumulatedLatency = 0;
    this.accumulatedPacketLoss = 0;
    this.lastPauseEnd = 0;
    this.lastVolumeSpike = 0;
    this.qualityMetrics = {
      mosScore: 4.0,
      jitter: 0,
      packetLoss: 0,
      latency: 0,
      clarity: 1.0,
      noiseLevel: 0,
    };
    this.updateState('idle');
  }

  private updateState(newState: AnalysisState): void {
    if (this.state !== newState) {
      this.state = newState;
      this.emit('state:changed', newState);
    }
  }

  destroy(): void {
    this.reset();
    this.removeAllListeners();
  }
}

export default CallAnalysisEngine;
