/**
 * Real-Time Call Analysis Server
 * Integrates WebRTC audio capture, inference engine, and WebSocket alerts
 * Provides live call analysis with real-time event broadcasting
 */

import { WebSocketServer } from 'ws';
import { EventEmitter } from 'events';
import { AudioStreamCapture, AudioFrame } from '../audio/audio-stream-capture';
import { CallAnalysisEngine, AnalysisResult, CallEvent, Anomaly } from '../inference/call-analysis-engine';

export interface CallAnalysisServerOptions {
  port: number;
  enableEchoCancellation?: boolean;
  enableNoiseSuppression?: boolean;
  enableAutoGainControl?: boolean;
  analysisConfig?: {
    sentimentWindowMs?: number;
    interruptThresholdMs?: number;
    overlapThresholdMs?: number;
    pauseThresholdMs?: number;
    volumeSpikeThreshold?: number;
    anomalySensitivity?: 'low' | 'medium' | 'high';
    enableSpeakerDiarization?: boolean;
  };
}

export interface ClientSession {
  clientId: string;
  ws: WebSocket;
  capture: AudioStreamCapture;
  engine: CallAnalysisEngine;
  startedAt: Date;
}

export type ServerEventType =
  | 'client:connected'
  | 'client:disconnected'
  | 'analysis:result'
  | 'analysis:alert'
  | 'analysis:error';

export interface ServerEvent {
  type: ServerEventType;
  clientId: string;
  timestamp: number;
  payload: any;
}

export interface Alert {
  alertType: 'interrupt' | 'overlap' | 'long_pause' | 'volume_spike' | 'silence' | 'speaker_change' | 'anomaly';
  severity: 'low' | 'medium' | 'high' | 'critical';
  message: string;
  metadata: Record<string, unknown>;
}

const DEFAULT_OPTIONS: Partial<CallAnalysisServerOptions> = {
  port: 8089,
  enableEchoCancellation: true,
  enableNoiseSuppression: true,
  enableAutoGainControl: true,
};

class WebSocket {
  private conn: any;
  private listeners: Map<string, Set<(...args: any[]) => void>> = new Map();

  constructor(conn: any) {
    this.conn = conn;

    this.conn.on('message', (data: Buffer) => {
      this.emit('message', data);
    });

    this.conn.on('close', () => {
      this.emit('close');
    });

    this.conn.on('error', (error: Error) => {
      this.emit('error', error);
    });
  }

  send(data: any): void {
    if (this.conn.readyState === 1) { // OPEN
      this.conn.send(data);
    }
  }

  on(event: string, callback: (...args: any[]) => void): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(callback);
  }

  off(event: string, callback: (...args: any[]) => void): void {
    this.listeners.get(event)?.delete(callback);
  }

  private emit(event: string, ...args: any[]): void {
    this.listeners.get(event)?.forEach(cb => cb(...args));
  }
}

export class RealTimeCallAnalysisServer extends EventEmitter {
  private wss: WebSocketServer;
  private options: CallAnalysisServerOptions;
  private sessions: Map<string, ClientSession> = new Map();
  private port: number;
  private running: boolean = false;

  constructor(options: CallAnalysisServerOptions = DEFAULT_OPTIONS as CallAnalysisServerOptions) {
    super();
    this.options = { ...DEFAULT_OPTIONS, ...options } as CallAnalysisServerOptions;
    this.port = this.options.port;
    this.wss = new WebSocketServer({ port: this.port });
  }

  async start(): Promise<void> {
    try {
      this.wss.on('connection', (ws: any, req) => {
        const url = new URL(req.url || '', `http://${req.headers.host}`);
        const clientId = url.searchParams.get('clientId') || `client-${Date.now()}`;

        console.log(`Call analysis client connected: ${clientId}`);

        const session = this.createClientSession(clientId, ws);
        this.sessions.set(clientId, session);

        this.emit('client:connected', { clientId, timestamp: Date.now() });

        ws.on('message', (data: Buffer) => {
          this.handleClientMessage(clientId, data);
        });

        ws.on('close', () => {
          console.log(`Call analysis client disconnected: ${clientId}`);
          this.removeClientSession(clientId);
        });

        // Send connection confirmation
        ws.send(JSON.stringify({
          type: 'connected',
          clientId,
          timestamp: Date.now(),
        }));
      });

      this.running = true;
      console.log(`Real-Time Call Analysis Server running on port ${this.port}`);
    } catch (error) {
      console.error('Failed to start Call Analysis Server:', error);
      this.emit('analysis:error', error);
      throw error;
    }
  }

  private createClientSession(clientId: string, ws: any): ClientSession {
    const capture = new AudioStreamCapture({
      sampleRate: 16000,
      channelCount: 1,
      frameSize: 1024,
      enableEchoCancellation: this.options.enableEchoCancellation,
      enableNoiseSuppression: this.options.enableNoiseSuppression,
      enableAutoGainControl: this.options.enableAutoGainControl,
    });

    const engine = new CallAnalysisEngine({
      sentimentWindowMs: this.options.analysisConfig?.sentimentWindowMs || 5000,
      interruptThresholdMs: this.options.analysisConfig?.interruptThresholdMs || 200,
      overlapThresholdMs: this.options.analysisConfig?.overlapThresholdMs || 300,
      pauseThresholdMs: this.options.analysisConfig?.pauseThresholdMs || 2000,
      volumeSpikeThreshold: this.options.analysisConfig?.volumeSpikeThreshold || 0.8,
      anomalySensitivity: this.options.analysisConfig?.anomalySensitivity || 'medium',
      enableSpeakerDiarization: this.options.analysisConfig?.enableSpeakerDiarization || false,
    });

    // Pipe audio frames to analysis engine
    capture.on('frame', (frame: AudioFrame) => {
      const result = engine.processFrame({
        samples: frame.samples,
        sampleRate: frame.sampleRate,
        timestamp: frame.timestamp,
        rmsLevel: frame.rmsLevel,
        duration: frame.duration,
      });

      this.emitAnalysisResult(clientId, result);
    });

    // Forward alerts from analysis engine
    engine.on('events', (events: CallEvent[]) => {
      const alerts = events.map((e: CallEvent) => ({
        alertType: e.type as Alert['alertType'],
        severity: e.severity as Alert['severity'],
        message: this.formatEventMessage(e.type, e.metadata),
        metadata: e.metadata,
      }));

      alerts.forEach((alert: { alertType: Alert['alertType']; severity: Alert['severity']; message: string; metadata: Record<string, unknown> }) => {
        this.emit('analysis:alert', { clientId, alert, timestamp: Date.now() });
        this.sendToClient(clientId, { msgType: 'alert', ...alert });
      });
    });

    engine.on('anomalies', (anomalies: Anomaly[]) => {
      anomalies.forEach((anomaly: Anomaly) => {
        const alert: Omit<Alert, 'alertType'> = {
          severity: anomaly.severity,
          message: anomaly.description,
          metadata: { anomalyType: anomaly.type, ...anomaly },
        };

        this.emit('analysis:alert', { clientId, alert, timestamp: Date.now() });
        this.sendToClient(clientId, { msgType: 'anomaly', alertType: 'anomaly', ...alert });
      });
    });

    return {
      clientId,
      ws: new WebSocket(ws),
      capture,
      engine,
      startedAt: new Date(),
    };
  }

  private async handleClientMessage(clientId: string, data: Buffer): Promise<void> {
    const session = this.sessions.get(clientId);
    if (!session) {
      this.sendToClient(clientId, { type: 'error', message: 'Session not found' });
      return;
    }

    try {
      const message = JSON.parse(data.toString());

      switch (message.type) {
        case 'start':
          await this.startCapture(clientId);
          break;

        case 'stop':
          await this.stopCapture(clientId);
          break;

        case 'pause':
          await session.capture.pause();
          this.sendToClient(clientId, { type: 'paused' });
          break;

        case 'resume':
          await session.capture.resume();
          this.sendToClient(clientId, { type: 'resumed' });
          break;

        case 'getMetrics':
          const metrics = session.capture.getMetrics();
          const quality = session.engine.getQualityMetrics();
          this.sendToClient(clientId, { type: 'metrics', audio: metrics, callQuality: quality });
          break;

        case 'getEvents':
          const events = session.engine.getEvents();
          this.sendToClient(clientId, { type: 'events', events });
          break;

        case 'reset':
          session.engine.reset();
          this.sendToClient(clientId, { type: 'reset' });
          break;

        default:
          this.sendToClient(clientId, { type: 'error', message: `Unknown message type: ${message.type}` });
      }
    } catch (error) {
      console.error(`Error handling message from ${clientId}:`, error);
      this.sendToClient(clientId, { type: 'error', message: String(error) });
    }
  }

  private async startCapture(clientId: string): Promise<void> {
    const session = this.sessions.get(clientId);
    if (!session) {
      throw new Error(`Session not found: ${clientId}`);
    }

    try {
      await session.capture.startFromMicrophone();
      this.sendToClient(clientId, { type: 'capturing', timestamp: Date.now() });
      this.emit('analysis:result', { clientId, status: 'capturing', timestamp: Date.now() });
    } catch (error) {
      console.error(`Failed to start capture for ${clientId}:`, error);
      this.sendToClient(clientId, { type: 'error', message: `Capture failed: ${String(error)}` });
      this.emit('analysis:error', { clientId, error });
    }
  }

  private async stopCapture(clientId: string): Promise<void> {
    const session = this.sessions.get(clientId);
    if (!session) {
      throw new Error(`Session not found: ${clientId}`);
    }

    await session.capture.stop();
    this.sendToClient(clientId, { type: 'stopped', timestamp: Date.now() });
    this.emit('analysis:result', { clientId, status: 'stopped', timestamp: Date.now() });
  }

  private emitAnalysisResult(clientId: string, result: AnalysisResult): void {
    // Only emit significant results to reduce bandwidth
    if (result.events.length > 0 || result.anomalies.length > 0 || result.callQuality.mosScore < 3.5) {
      this.sendToClient(clientId, {
        type: 'analysis',
        timestamp: result.timestamp,
        callQuality: result.callQuality,
        sentiment: result.sentiment,
        events: result.events,
        anomalies: result.anomalies,
        summary: result.summary,
      });
    }
  }

  private formatEventMessage(type: string, metadata: Record<string, unknown>): string {
    switch (type) {
      case 'interrupt':
        return 'Speaker interrupt detected';
      case 'overlap':
        return 'Simultaneous speaking detected';
      case 'long_pause':
        return `Extended pause: ${(metadata.pauseDuration as number) / 1000}s`;
      case 'volume_spike':
        return `Volume spike detected (level: ${(metadata.level as number) * 100}%)`;
      case 'silence':
        return 'Silence detected';
      case 'speaker_change':
        return 'Speaker change detected';
      default:
        return `Event: ${type}`;
    }
  }

  private sendToClient(clientId: string, message: object): void {
    const session = this.sessions.get(clientId);
    if (session) {
      session.ws.send(JSON.stringify(message));
    }
  }

  private removeClientSession(clientId: string): void {
    const session = this.sessions.get(clientId);
    if (session) {
      session.capture.destroy();
      session.engine.destroy();
      this.sessions.delete(clientId);
      this.emit('client:disconnected', { clientId, timestamp: Date.now() });
    }
  }

  getClientCount(): number {
    return this.sessions.size;
  }

  getClients(): string[] {
    return Array.from(this.sessions.keys());
  }

  getSession(clientId: string): ClientSession | undefined {
    return this.sessions.get(clientId);
  }

  async stop(): Promise<void> {
    this.sessions.forEach((_, clientId) => this.removeClientSession(clientId));
    
    return new Promise((resolve, reject) => {
      this.wss.close((err?: Error) => {
        if (err) {
          console.error('Error stopping Call Analysis Server:', err);
          reject(err);
        } else {
          this.running = false;
          console.log('Real-Time Call Analysis Server stopped');
          resolve();
        }
      });
    });
  }

  isRunning(): boolean {
    return this.running;
  }
}

export default RealTimeCallAnalysisServer;
