/**
 * WebRTC Manager
 * Handles all WebRTC peer connection logic for voice and video calls
 */

class WebRTCManager {
  constructor(socket) {
    this.socket = socket;
    this.peerConnections = new Map(); // Map of userId -> RTCPeerConnection
    this.localStream = null;
    this.screenStream = null;
    this.remoteStreams = new Map(); // Map of userId -> MediaStream
    this.currentCallId = null;
    this.isInitiator = false;
    
    // WebRTC configuration with STUN/TURN servers
    this.configuration = {
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' }
      ]
    };
    
    // Media constraints
    this.audioConstraints = {
      echoCancellation: true,
      noiseSuppression: true,
      autoGainControl: true
    };
    
    this.videoConstraints = {
      width: { ideal: 1280 },
      height: { ideal: 720 },
      frameRate: { ideal: 30 }
    };
    
    // Event handlers
    this.onRemoteStream = null;
    this.onRemoteStreamRemoved = null;
    this.onConnectionStateChange = null;
    this.onIceConnectionStateChange = null;
    
    this.setupSocketListeners();
  }
  
  /**
   * Setup Socket.io listeners for WebRTC signaling
   */
  setupSocketListeners() {
    this.socket.on('call:offer', async (data) => {
      await this.handleOffer(data);
    });
    
    this.socket.on('call:answer', async (data) => {
      await this.handleAnswer(data);
    });
    
    this.socket.on('call:ice-candidate', async (data) => {
      await this.handleIceCandidate(data);
    });
    
    this.socket.on('call:ended', () => {
      this.cleanup();
    });
    
    this.socket.on('call:participant-joined', async (data) => {
      console.log('Participant joined:', data);
      // For group calls, establish connection with new participant
      if (this.isInitiator) {
        await this.initiateCallToUser(data.userId);
      }
    });
    
    this.socket.on('call:participant-left', (data) => {
      console.log('Participant left:', data);
      this.removePeerConnection(data.userId);
    });
    
    this.socket.on('call:media-state-changed', (data) => {
      console.log('Media state changed:', data);
      // Update UI to reflect remote user's media state
      if (this.onMediaStateChanged) {
        this.onMediaStateChanged(data);
      }
    });
  }
  
  /**
   * Set TURN server credentials
   */
  async setTurnCredentials(turnCredentials) {
    if (turnCredentials && turnCredentials.urls) {
      const turnServer = {
        urls: turnCredentials.urls,
        username: turnCredentials.username,
        credential: turnCredentials.credential
      };
      
      this.configuration.iceServers.push(turnServer);
      console.log('TURN server configured');
    }
  }
  
  /**
   * Initialize local media stream (audio and/or video)
   */
  async initializeLocalStream(audioEnabled = true, videoEnabled = true) {
    try {
      const constraints = {
        audio: audioEnabled ? this.audioConstraints : false,
        video: videoEnabled ? this.videoConstraints : false
      };
      
      this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
      console.log('Local stream initialized:', {
        audio: audioEnabled,
        video: videoEnabled,
        tracks: this.localStream.getTracks().length
      });
      
      return this.localStream;
    } catch (error) {
      console.error('Error accessing media devices:', error);
      throw new Error(`Failed to access camera/microphone: ${error.message}`);
    }
  }
  
  /**
   * Create a peer connection for a user
   */
  createPeerConnection(userId) {
    if (this.peerConnections.has(userId)) {
      return this.peerConnections.get(userId);
    }
    
    const peerConnection = new RTCPeerConnection(this.configuration);
    
    // Add local stream tracks to peer connection
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => {
        peerConnection.addTrack(track, this.localStream);
      });
    }
    
    // Handle incoming remote stream
    peerConnection.ontrack = (event) => {
      console.log('Received remote track from', userId, event.track.kind);
      
      const [remoteStream] = event.streams;
      this.remoteStreams.set(userId, remoteStream);
      
      if (this.onRemoteStream) {
        this.onRemoteStream(userId, remoteStream);
      }
    };
    
    // Handle ICE candidates
    peerConnection.onicecandidate = (event) => {
      if (event.candidate) {
        console.log('Sending ICE candidate to', userId);
        this.socket.emit('call:ice-candidate', {
          callId: this.currentCallId,
          targetUserId: userId,
          candidate: event.candidate
        });
      }
    };
    
    // Handle connection state changes
    peerConnection.onconnectionstatechange = () => {
      console.log(`Connection state with ${userId}:`, peerConnection.connectionState);
      
      if (this.onConnectionStateChange) {
        this.onConnectionStateChange(userId, peerConnection.connectionState);
      }
      
      // Cleanup if connection fails or closes
      if (peerConnection.connectionState === 'failed' || 
          peerConnection.connectionState === 'closed') {
        this.removePeerConnection(userId);
      }
    };
    
    // Handle ICE connection state changes
    peerConnection.oniceconnectionstatechange = () => {
      console.log(`ICE connection state with ${userId}:`, peerConnection.iceConnectionState);
      
      if (this.onIceConnectionStateChange) {
        this.onIceConnectionStateChange(userId, peerConnection.iceConnectionState);
      }
    };
    
    this.peerConnections.set(userId, peerConnection);
    return peerConnection;
  }
  
  /**
   * Initiate a call to a user (create offer)
   */
  async initiateCallToUser(userId) {
    try {
      const peerConnection = this.createPeerConnection(userId);
      
      // Create offer
      const offer = await peerConnection.createOffer({
        offerToReceiveAudio: true,
        offerToReceiveVideo: true
      });
      
      await peerConnection.setLocalDescription(offer);
      
      // Send offer through signaling server
      this.socket.emit('call:offer', {
        callId: this.currentCallId,
        targetUserId: userId,
        offer: offer
      });
      
      console.log('Call offer sent to', userId);
    } catch (error) {
      console.error('Error initiating call:', error);
      throw error;
    }
  }
  
  /**
   * Handle incoming call offer
   */
  async handleOffer(data) {
    const { callId, fromUserId, offer } = data;
    
    try {
      console.log('Received call offer from', fromUserId);
      
      this.currentCallId = callId;
      const peerConnection = this.createPeerConnection(fromUserId);
      
      await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
      
      // Create answer
      const answer = await peerConnection.createAnswer();
      await peerConnection.setLocalDescription(answer);
      
      // Send answer back
      this.socket.emit('call:answer', {
        callId: callId,
        targetUserId: fromUserId,
        answer: answer
      });
      
      console.log('Call answer sent to', fromUserId);
    } catch (error) {
      console.error('Error handling offer:', error);
      throw error;
    }
  }
  
  /**
   * Handle incoming call answer
   */
  async handleAnswer(data) {
    const { fromUserId, answer } = data;
    
    try {
      console.log('Received call answer from', fromUserId);
      
      const peerConnection = this.peerConnections.get(fromUserId);
      if (!peerConnection) {
        throw new Error(`No peer connection found for user ${fromUserId}`);
      }
      
      await peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
      console.log('Remote description set for', fromUserId);
    } catch (error) {
      console.error('Error handling answer:', error);
      throw error;
    }
  }
  
  /**
   * Handle incoming ICE candidate
   */
  async handleIceCandidate(data) {
    const { fromUserId, candidate } = data;
    
    try {
      const peerConnection = this.peerConnections.get(fromUserId);
      if (!peerConnection) {
        console.warn(`No peer connection found for user ${fromUserId}`);
        return;
      }
      
      await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
      console.log('ICE candidate added for', fromUserId);
    } catch (error) {
      console.error('Error adding ICE candidate:', error);
    }
  }
  
  /**
   * Remove peer connection for a user
   */
  removePeerConnection(userId) {
    const peerConnection = this.peerConnections.get(userId);
    if (peerConnection) {
      peerConnection.close();
      this.peerConnections.delete(userId);
    }
    
    const remoteStream = this.remoteStreams.get(userId);
    if (remoteStream) {
      remoteStream.getTracks().forEach(track => track.stop());
      this.remoteStreams.delete(userId);
      
      if (this.onRemoteStreamRemoved) {
        this.onRemoteStreamRemoved(userId);
      }
    }
    
    console.log('Peer connection removed for', userId);
  }
  
  /**
   * Toggle audio track enabled/disabled
   */
  toggleAudio(enabled) {
    if (this.localStream) {
      const audioTrack = this.localStream.getAudioTracks()[0];
      if (audioTrack) {
        audioTrack.enabled = enabled;
        console.log('Audio', enabled ? 'enabled' : 'disabled');
        return true;
      }
    }
    return false;
  }
  
  /**
   * Toggle video track enabled/disabled
   */
  toggleVideo(enabled) {
    if (this.localStream) {
      const videoTrack = this.localStream.getVideoTracks()[0];
      if (videoTrack) {
        videoTrack.enabled = enabled;
        console.log('Video', enabled ? 'enabled' : 'disabled');
        return true;
      }
    }
    return false;
  }
  
  /**
   * Start screen sharing
   */
  async startScreenShare() {
    try {
      this.screenStream = await navigator.mediaDevices.getDisplayMedia({
        video: {
          cursor: 'always'
        },
        audio: false
      });
      
      const screenTrack = this.screenStream.getVideoTracks()[0];
      
      // Replace video track in all peer connections
      this.peerConnections.forEach((peerConnection) => {
        const sender = peerConnection.getSenders().find(s => s.track?.kind === 'video');
        if (sender) {
          sender.replaceTrack(screenTrack);
        }
      });
      
      // Handle screen share stop
      screenTrack.onended = () => {
        this.stopScreenShare();
      };
      
      console.log('Screen sharing started');
      return this.screenStream;
    } catch (error) {
      console.error('Error starting screen share:', error);
      throw error;
    }
  }
  
  /**
   * Stop screen sharing and restore camera
   */
  stopScreenShare() {
    if (this.screenStream) {
      this.screenStream.getTracks().forEach(track => track.stop());
      this.screenStream = null;
      
      // Restore camera track
      if (this.localStream) {
        const videoTrack = this.localStream.getVideoTracks()[0];
        if (videoTrack) {
          this.peerConnections.forEach((peerConnection) => {
            const sender = peerConnection.getSenders().find(s => s.track?.kind === 'video');
            if (sender) {
              sender.replaceTrack(videoTrack);
            }
          });
        }
      }
      
      console.log('Screen sharing stopped');
    }
  }
  
  /**
   * Get connection statistics
   */
  async getConnectionStats(userId) {
    const peerConnection = this.peerConnections.get(userId);
    if (!peerConnection) {
      return null;
    }
    
    const stats = await peerConnection.getStats();
    const result = {
      audio: {},
      video: {},
      connection: {}
    };
    
    stats.forEach(report => {
      if (report.type === 'inbound-rtp') {
        if (report.kind === 'audio') {
          result.audio.bytesReceived = report.bytesReceived;
          result.audio.packetsLost = report.packetsLost;
          result.audio.jitter = report.jitter;
        } else if (report.kind === 'video') {
          result.video.bytesReceived = report.bytesReceived;
          result.video.packetsLost = report.packetsLost;
          result.video.framesDecoded = report.framesDecoded;
          result.video.frameWidth = report.frameWidth;
          result.video.frameHeight = report.frameHeight;
        }
      } else if (report.type === 'candidate-pair' && report.state === 'succeeded') {
        result.connection.roundTripTime = report.currentRoundTripTime;
        result.connection.availableOutgoingBitrate = report.availableOutgoingBitrate;
      }
    });
    
    return result;
  }
  
  /**
   * Cleanup all connections and streams
   */
  cleanup() {
    console.log('Cleaning up WebRTC resources');
    
    // Stop screen share if active
    this.stopScreenShare();
    
    // Close all peer connections
    this.peerConnections.forEach((peerConnection, userId) => {
      this.removePeerConnection(userId);
    });
    
    // Stop local stream
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
      this.localStream = null;
    }
    
    // Clear remote streams
    this.remoteStreams.forEach((stream) => {
      stream.getTracks().forEach(track => track.stop());
    });
    this.remoteStreams.clear();
    
    this.currentCallId = null;
    this.isInitiator = false;
  }
  
  /**
   * Get local stream
   */
  getLocalStream() {
    return this.localStream;
  }
  
  /**
   * Get remote stream for a user
   */
  getRemoteStream(userId) {
    return this.remoteStreams.get(userId);
  }
  
  /**
   * Get all remote streams
   */
  getAllRemoteStreams() {
    return Array.from(this.remoteStreams.entries());
  }
  
  /**
   * Check if audio is enabled
   */
  isAudioEnabled() {
    if (this.localStream) {
      const audioTrack = this.localStream.getAudioTracks()[0];
      return audioTrack ? audioTrack.enabled : false;
    }
    return false;
  }
  
  /**
   * Check if video is enabled
   */
  isVideoEnabled() {
    if (this.localStream) {
      const videoTrack = this.localStream.getVideoTracks()[0];
      return videoTrack ? videoTrack.enabled : false;
    }
    return false;
  }
  
  /**
   * Check if screen sharing is active
   */
  isScreenSharing() {
    return this.screenStream !== null;
  }
}

export default WebRTCManager;
