/**
 * WebRTC Manager
 * Handles peer connections for voice and video calls
 */

import { io, Socket } from 'socket.io-client';

export interface PeerConnectionConfig {
  iceServers?: RTCIceServer[];
  audioConstraints?: MediaTrackConstraints;
  videoConstraints?: MediaTrackConstraints;
}

export interface CallOptions {
  audio: boolean;
  video: boolean;
}

export class WebRTCManager {
  private socket: Socket | null = null;
  private peerConnections: Map<string, RTCPeerConnection> = new Map();
  private localStream: MediaStream | null = null;
  private config: RTCConfiguration;
  private audioContext: AudioContext | null = null;
  private analyser: AnalyserNode | null = null;
  
  constructor(serverUrl: string, config?: PeerConnectionConfig) {
    this.config = {
      iceServers: config?.iceServers || [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' },
      ],
    };
    
    this.socket = io(serverUrl, {
      transports: ['websocket'],
    });
    
    this.setupSocketListeners();
  }
  
  /**
   * Setup Socket.IO event listeners for signaling
   */
  private setupSocketListeners(): void {
    if (!this.socket) return;
    
    this.socket.on('call:offer', async ({ from, offer }) => {
      await this.handleOffer(from, offer);
    });
    
    this.socket.on('call:answer', async ({ from, answer }) => {
      await this.handleAnswer(from, answer);
    });
    
    this.socket.on('call:ice-candidate', ({ from, candidate }) => {
      this.handleIceCandidate(from, candidate);
    });
    
    this.socket.on('call:end', ({ from }) => {
      this.closePeerConnection(from);
    });
  }
  
  /**
   * Initialize local media stream
   */
  async initializeLocalStream(options: CallOptions): Promise<MediaStream> {
    try {
      const constraints: MediaStreamConstraints = {
        audio: options.audio ? {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true,
        } : false,
        video: options.video ? {
          width: { ideal: 1280 },
          height: { ideal: 720 },
          frameRate: { ideal: 30 },
        } : false,
      };
      
      this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
      
      // Setup voice activity detection
      if (options.audio) {
        this.setupVoiceActivityDetection();
      }
      
      return this.localStream;
    } catch (error) {
      console.error('Failed to get user media:', error);
      throw error;
    }
  }
  
  /**
   * Setup voice activity detection using Web Audio API
   */
  private setupVoiceActivityDetection(): void {
    if (!this.localStream) return;
    
    this.audioContext = new AudioContext();
    this.analyser = this.audioContext.createAnalyser();
    this.analyser.fftSize = 2048;
    
    const source = this.audioContext.createMediaStreamSource(this.localStream);
    source.connect(this.analyser);
  }
  
  /**
   * Check if user is speaking
   */
  isSpeaking(threshold: number = 0.01): boolean {
    if (!this.analyser) return false;
    
    const dataArray = new Uint8Array(this.analyser.frequencyBinCount);
    this.analyser.getByteFrequencyData(dataArray);
    
    const average = dataArray.reduce((acc, val) => acc + val, 0) / dataArray.length;
    return average / 255 > threshold;
  }
  
  /**
   * Initiate a call to a peer
   */
  async initiateCall(peerId: string, options: CallOptions): Promise<void> {
    if (!this.socket) throw new Error('Socket not initialized');
    
    // Get local stream
    if (!this.localStream) {
      await this.initializeLocalStream(options);
    }
    
    // Create peer connection
    const pc = this.createPeerConnection(peerId);
    
    // Add local tracks
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => {
        pc.addTrack(track, this.localStream!);
      });
    }
    
    // Create and send offer
    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);
    
    this.socket.emit('call:offer', {
      to: peerId,
      offer: offer,
    });
  }
  
  /**
   * Handle incoming call offer
   */
  private async handleOffer(peerId: string, offer: RTCSessionDescriptionInit): Promise<void> {
    if (!this.socket) return;
    
    const pc = this.createPeerConnection(peerId);
    
    await pc.setRemoteDescription(new RTCSessionDescription(offer));
    
    // Add local tracks if available
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => {
        pc.addTrack(track, this.localStream!);
      });
    }
    
    // Create and send answer
    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);
    
    this.socket.emit('call:answer', {
      to: peerId,
      answer: answer,
    });
  }
  
  /**
   * Handle answer to our offer
   */
  private async handleAnswer(peerId: string, answer: RTCSessionDescriptionInit): Promise<void> {
    const pc = this.peerConnections.get(peerId);
    if (!pc) return;
    
    await pc.setRemoteDescription(new RTCSessionDescription(answer));
  }
  
  /**
   * Handle ICE candidate
   */
  private async handleIceCandidate(peerId: string, candidate: RTCIceCandidateInit): Promise<void> {
    const pc = this.peerConnections.get(peerId);
    if (!pc) return;
    
    try {
      await pc.addIceCandidate(new RTCIceCandidate(candidate));
    } catch (error) {
      console.error('Error adding ICE candidate:', error);
    }
  }
  
  /**
   * Create a new peer connection
   */
  private createPeerConnection(peerId: string): RTCPeerConnection {
    const pc = new RTCPeerConnection(this.config);
    
    // ICE candidate event
    pc.onicecandidate = (event) => {
      if (event.candidate && this.socket) {
        this.socket.emit('call:ice-candidate', {
          to: peerId,
          candidate: event.candidate,
        });
      }
    };
    
    // Track event (remote stream)
    pc.ontrack = (event) => {
      this.onRemoteTrack?.(peerId, event.streams[0]);
    };
    
    // Connection state change
    pc.onconnectionstatechange = () => {
      console.log(`Peer connection state: ${pc.connectionState}`);
      
      if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
        this.closePeerConnection(peerId);
      }
    };
    
    this.peerConnections.set(peerId, pc);
    return pc;
  }
  
  /**
   * Close peer connection
   */
  private closePeerConnection(peerId: string): void {
    const pc = this.peerConnections.get(peerId);
    if (pc) {
      pc.close();
      this.peerConnections.delete(peerId);
      this.onPeerDisconnected?.(peerId);
    }
  }
  
  /**
   * End call
   */
  endCall(peerId: string): void {
    if (this.socket) {
      this.socket.emit('call:end', { to: peerId });
    }
    this.closePeerConnection(peerId);
  }
  
  /**
   * Toggle audio
   */
  toggleAudio(enabled: boolean): void {
    if (!this.localStream) return;
    
    this.localStream.getAudioTracks().forEach(track => {
      track.enabled = enabled;
    });
  }
  
  /**
   * Toggle video
   */
  toggleVideo(enabled: boolean): void {
    if (!this.localStream) return;
    
    this.localStream.getVideoTracks().forEach(track => {
      track.enabled = enabled;
    });
  }
  
  /**
   * Get screen sharing stream
   */
  async getScreenStream(): Promise<MediaStream> {
    try {
      return await navigator.mediaDevices.getDisplayMedia({
        video: {
          cursor: 'always',
        },
        audio: false,
      });
    } catch (error) {
      console.error('Failed to get screen stream:', error);
      throw error;
    }
  }
  
  /**
   * Replace video track with screen share
   */
  async startScreenShare(peerId: string): Promise<void> {
    const screenStream = await this.getScreenStream();
    const screenTrack = screenStream.getVideoTracks()[0];
    
    const pc = this.peerConnections.get(peerId);
    if (!pc) return;
    
    const sender = pc.getSenders().find(s => s.track?.kind === 'video');
    if (sender) {
      await sender.replaceTrack(screenTrack);
    }
    
    // Stop screen share when track ends
    screenTrack.onended = () => {
      this.stopScreenShare(peerId);
    };
  }
  
  /**
   * Stop screen sharing
   */
  async stopScreenShare(peerId: string): Promise<void> {
    if (!this.localStream) return;
    
    const videoTrack = this.localStream.getVideoTracks()[0];
    const pc = this.peerConnections.get(peerId);
    
    if (!pc) return;
    
    const sender = pc.getSenders().find(s => s.track?.kind === 'video');
    if (sender && videoTrack) {
      await sender.replaceTrack(videoTrack);
    }
  }
  
  /**
   * Cleanup
   */
  destroy(): void {
    // Close all peer connections
    this.peerConnections.forEach((pc, peerId) => {
      this.closePeerConnection(peerId);
    });
    
    // Stop local stream
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
      this.localStream = null;
    }
    
    // Close audio context
    if (this.audioContext) {
      this.audioContext.close();
      this.audioContext = null;
    }
    
    // Disconnect socket
    if (this.socket) {
      this.socket.disconnect();
      this.socket = null;
    }
  }
  
  // Event handlers (to be set by consumer)
  onRemoteTrack?: (peerId: string, stream: MediaStream) => void;
  onPeerDisconnected?: (peerId: string) => void;
}
