/**
 * WebRTC Video Manager
 * Handles P2P video connections using PeerJS
 * Manages peer connections, streams, and signaling
 */

import { Peer } from 'peerjs';
import { EventEmitter } from 'events';

export type PeerConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting' | 'error';

export type ConnectionQuality = 'excellent' | 'good' | 'fair' | 'poor';

export interface WebRTCVideoManagerOptions {
  peerId?: string;
  serverUrl?: string;
  audioEnabled?: boolean;
  videoEnabled?: boolean;
  turnServers?: RTCIceServer[];
}

export interface PeerConnection {
  peerId: string;
  connection: MediaConnection;
  stream: MediaStream | null;
  state: PeerConnectionState;
  quality: ConnectionQuality;
  connectedAt: Date;
}

export interface VideoManagerEvents {
  'peer:connected': (peerId: string, stream: MediaStream) => void;
  'peer:disconnected': (peerId: string) => void;
  'peer:error': (peerId: string, error: Error) => void;
  'local:stream': (stream: MediaStream) => void;
  'connection:quality': (peerId: string, quality: ConnectionQuality) => void;
  'state:changed': (state: PeerConnectionState) => void;
}

class MediaConnection {
  private conn: any;
  private stream: MediaStream | null = null;
  private listeners: Map<string, Set<(...args: any[]) => void>> = new Map();

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

    this.conn.on('stream', (stream: MediaStream) => {
      this.stream = stream;
      this.emit('stream', stream);
    });

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

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

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

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

  send(data: any): void {
    this.conn.send(data);
  }

  getStream(): MediaStream | null {
    return this.stream;
  }

  close(): void {
    this.conn.close();
  }

  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 WebRTCVideoManager extends EventEmitter {
  private peer: Peer | null = null;
  private options: WebRTCVideoManagerOptions;
  private connections: Map<string, MediaConnection> = new Map();
  private localStream: MediaStream | null = null;
  private state: PeerConnectionState = 'disconnected';
  private qualityMetrics: Map<string, ConnectionQuality> = new Map();

  constructor(options: WebRTCVideoManagerOptions = {}) {
    super();
    this.options = {
      peerId: options.peerId,
      serverUrl: options.serverUrl || 'https://0.peerjs.com:443',
      audioEnabled: options.audioEnabled ?? true,
      videoEnabled: options.videoEnabled ?? true,
      turnServers: options.turnServers || [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' },
      ],
    };
  }

  async initialize(): Promise<void> {
    try {
      // Get local media stream
      await this.acquireLocalStream();

      // Initialize PeerJS
      const serverUrl = new URL(this.options.serverUrl ?? 'https://0.peerjs.com:443');
      this.peer = new Peer(this.options.peerId ?? 'unknown', {
        host: serverUrl.hostname,
        port: Number(serverUrl.port || 443),
        path: '/webrtc',
        secure: serverUrl.protocol === 'https:',
        config: {
          iceServers: this.options.turnServers,
        },
      });

      // Handle incoming connections
      this.peer.on('call', (conn: any) => {
        const mediaConn = new MediaConnection(conn);
        this.connections.set(conn.peer, mediaConn);

        this.emit('peer:connected', conn.peer, conn.stream);
        this.updateState('connected');
      });

      // Handle peer errors
      this.peer.on('error', (error: any) => {
        this.emit('peer:error', this.peer!.id, error);
        this.updateState('error');
      });

      // Handle connection open
      this.peer.on('open', (id: string) => {
        console.log('PeerJS initialized with ID:', id);
        this.updateState('connected');
      });

      // Handle connection close
      this.peer.on('close', () => {
        this.updateState('disconnected');
      });

    } catch (error) {
      console.error('Failed to initialize WebRTC manager:', error);
      this.emit('peer:error', this.options.peerId || 'unknown', error as Error);
      this.updateState('error');
      throw error;
    }
  }

  async acquireLocalStream(): Promise<MediaStream> {
    try {
      const constraints: MediaStreamConstraints = {
        audio: this.options.audioEnabled,
        video: this.options.videoEnabled,
      };

      this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
      this.emit('local:stream', this.localStream);
      
      return this.localStream;
    } catch (error) {
      console.error('Failed to acquire local media stream:', error);
      throw error;
    }
  }

  connectToPeer(peerId: string, stream?: MediaStream): MediaConnection {
    if (!this.peer) {
      throw new Error('Peer not initialized. Call initialize() first.');
    }

    const conn = this.peer.call(peerId, stream ?? this.localStream!);
    const mediaConn = new MediaConnection(conn);
    this.connections.set(peerId, mediaConn);

    // Monitor connection quality
    mediaConn.on('open', () => {
      this.startQualityMonitoring(peerId, mediaConn);
    });

    mediaConn.on('close', () => {
      this.qualityMetrics.delete(peerId);
      this.emit('peer:disconnected', peerId);
    });

    return mediaConn;
  }

  getPeerConnection(peerId: string): MediaConnection | undefined {
    return this.connections.get(peerId);
  }

  getAllConnections(): Map<string, MediaConnection> {
    return new Map(this.connections);
  }

  disconnectFromPeer(peerId: string): void {
    const conn = this.connections.get(peerId);
    if (conn) {
      conn.close();
      this.connections.delete(peerId);
      this.emit('peer:disconnected', peerId);
    }
  }

  disconnectAll(): void {
    this.connections.forEach((conn, peerId) => {
      conn.close();
      this.emit('peer:disconnected', peerId);
    });
    this.connections.clear();
  }

  getLocalStream(): MediaStream | null {
    return this.localStream;
  }

  toggleAudio(): void {
    if (this.localStream) {
      const audioTrack = this.localStream.getAudioTracks()[0];
      if (audioTrack) {
        audioTrack.enabled = !audioTrack.enabled;
        this.options.audioEnabled = audioTrack.enabled;
        console.log(`Audio ${audioTrack.enabled ? 'enabled' : 'disabled'}`);
      }
    }
  }

  toggleVideo(): void {
    if (this.localStream) {
      const videoTrack = this.localStream.getVideoTracks()[0];
      if (videoTrack) {
        videoTrack.enabled = !videoTrack.enabled;
        this.options.videoEnabled = videoTrack.enabled;
        console.log(`Video ${videoTrack.enabled ? 'enabled' : 'disabled'}`);
      }
    }
  }

  isAudioEnabled(): boolean {
    if (this.localStream) {
      const audioTrack = this.localStream.getAudioTracks()[0];
      return audioTrack?.enabled ?? false;
    }
    return false;
  }

  isVideoEnabled(): boolean {
    if (this.localStream) {
      const videoTrack = this.localStream.getVideoTracks()[0];
      return videoTrack?.enabled ?? false;
    }
    return false;
  }

  getPeerId(): string | null {
    return this.peer?.id || null;
  }

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

  getConnectionQuality(peerId: string): ConnectionQuality {
    return this.qualityMetrics.get(peerId) || 'fair';
  }

  private startQualityMonitoring(peerId: string, conn: MediaConnection): void {
    const checkQuality = () => {
      // TODO: Implement actual RTCPeerConnection stats via getStats()
      const quality: ConnectionQuality = 'good';
      this.qualityMetrics.set(peerId, quality);
      this.emit('connection:quality', peerId, quality);
    };

    // Check quality every 5 seconds
    const interval = setInterval(checkQuality, 5000);
    checkQuality();

    // Cleanup on close
    conn.on('close', () => {
      clearInterval(interval);
    });
  }

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

  destroy(): void {
    this.disconnectAll();
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
      this.localStream = null;
    }
    if (this.peer) {
      this.peer.destroy();
      this.peer = null;
    }
    this.updateState('disconnected');
  }
}

export default WebRTCVideoManager;
