import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { ChatService } from './chat.service';
import { AuthService } from './auth.service';
import { UserService } from './user.service';
import { NotificationService } from './notification.service';
import { LoggerService } from './logger.service';
import { StompSubscription } from '@stomp/stompjs';
import axios from 'axios';

import { BASE_URL } from '../config/api.config';

export interface CallInfo {
  callId: string;
  senderId: string;
  senderName: string;
  senderAvatarUrl?: string;
  receiverId: string;
  receiverName: string;
  receiverAvatarUrl?: string;
  callType: 'video' | 'audio';
  callStatus: 'ringing' | 'accepted' | 'rejected' | 'ended' | 'cancelled';
  startedAt?: Date;
  endedAt?: Date;
}

export interface WebRtcSignal {
  callId: string;
  senderId: string;
  receiverId: string;
  signalType: 'offer' | 'answer' | 'ice-candidate';
  signalData: any;
}

export interface TurnConfig {
  server: string;
  port: number;
  username: string;
  password: string;
  realm: string;
  stunUrls: string;
  turnUrls: string;
}

@Injectable({
  providedIn: 'root'
})
export class CallService {
  private callSubject = new Subject<CallInfo>();
  private signalSubject = new Subject<WebRtcSignal>();
  private currentCall: CallInfo | null = null;
  private peerConnection: RTCPeerConnection | null = null;
  private localStream: MediaStream | null = null;
  private remoteStream: MediaStream | null = null;
  private currentUserId: string | null = null;
  private isInitiator: boolean = false;
  private callSubscription: StompSubscription | null = null;
  private signalSubscription: StompSubscription | null = null;
  private isSubscribedToCalls: boolean = false;
  private isSubscribedToSignals: boolean = false;
  private pendingIceCandidates: RTCIceCandidateInit[] = [];
  private remoteDescriptionSet: boolean = false;
  private callTimeoutId: any = null; // Timer for unanswered call timeout
  private callStartTime: Date | null = null; // Track when call started
  private turnConfig: TurnConfig | null = null; // Cache TURN config

  public call$ = this.callSubject.asObservable();
  public signal$ = this.signalSubject.asObservable();

  constructor(
    private chatService: ChatService,
    private authService: AuthService,
    private userService: UserService,
    private notificationService: NotificationService,
    private logger: LoggerService
  ) {
    this.setupCallSubscription();
    this.setupSignalSubscription();
    this.initCurrentUser();
    this.fetchTurnConfig();
  }
  
  private async fetchTurnConfig(): Promise<void> {
    try {
      const response = await axios.get(`${BASE_URL}/api/v3/turn/config`);
      this.turnConfig = response.data;
      this.logger.debug('TURN config loaded:', this.turnConfig);
    } catch (error) {
      this.logger.error('Failed to load TURN config, using fallback STUN servers:', error);
      // Use fallback config
      this.turnConfig = {
        server: 'localhost',
        port: 3478,
        username: 'telemedicine',
        password: 'changeme',
        realm: 'localdomain',
        stunUrls: 'stun:localhost:3478',
        turnUrls: 'turn:localhost:3478'
      };
    }
  }

  private async initCurrentUser(): Promise<void> {
    try {
      const user = await this.userService.getCurrentUser();
      if (user) {
        this.currentUserId = user.id;
      }
    } catch (error) {
      this.logger.error('Error getting current user for call service:', error);
    }
  }

  private setupCallSubscription(): void {
    // Subscribe to call events via chat service WebSocket
    const checkAndSubscribe = () => {
      if (this.chatService.isConnected()) {
        // Reset subscription flag if we were disconnected
        if (!this.isSubscribedToCalls) {
          this.subscribeToCalls();
        }
      } else {
        // Reset subscription flags when disconnected
        this.isSubscribedToCalls = false;
        this.callSubscription = null;
        setTimeout(checkAndSubscribe, 1000);
      }
    };
    checkAndSubscribe();
  }

  private subscribeToCalls(): void {
    // Prevent duplicate subscriptions
    if (this.isSubscribedToCalls && this.callSubscription) {
      this.logger.debug('Already subscribed to call events');
      return;
    }

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient || !this.chatService.isConnected()) {
      this.logger.debug('STOMP client not ready for call subscription');
      return;
    }

    // Use the same method as chat service to get queue identifier
    const userEmail = this.userService.getUserEmail();
    const queueIdentifier = userEmail;
    
    if (!queueIdentifier) {
      this.logger.error('User email not available for call subscription');
      return;
    }

    try {
      // Unsubscribe existing subscription if any
      if (this.callSubscription) {
        this.callSubscription.unsubscribe();
      }

      const subscription = stompClient.subscribe(`/user/queue/call`, (message: any) => {
        try {
          const callInfo: CallInfo = JSON.parse(message.body);
          this.logger.debug('Received call event:', callInfo);
          this.handleCallEvent(callInfo);
        } catch (error) {
          this.logger.error('Error parsing call notification:', error);
        }
      });
      
      this.callSubscription = subscription;
      this.isSubscribedToCalls = true;
      this.logger.debug('Subscribed to call events on queue:', `/user/queue/call`);
    } catch (error) {
      this.logger.error('Error creating call subscription:', error);
      this.isSubscribedToCalls = false;
    }
  }

  private setupSignalSubscription(): void {
    const checkAndSubscribe = () => {
      if (this.chatService.isConnected()) {
        // Reset subscription flag if we were disconnected
        if (!this.isSubscribedToSignals) {
          this.subscribeToSignals();
        }
      } else {
        // Reset subscription flags when disconnected
        this.isSubscribedToSignals = false;
        this.signalSubscription = null;
        setTimeout(checkAndSubscribe, 1000);
      }
    };
    checkAndSubscribe();
  }

  private subscribeToSignals(): void {
    // Prevent duplicate subscriptions
    if (this.isSubscribedToSignals && this.signalSubscription) {
      this.logger.debug('Already subscribed to signal events');
      return;
    }

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient || !this.chatService.isConnected()) {
      this.logger.debug('STOMP client not ready for signal subscription');
      return;
    }

    // Use the same method as chat service to get queue identifier
    const userEmail = this.userService.getUserEmail();
    const queueIdentifier = userEmail;
    
    if (!queueIdentifier) {
      this.logger.error('User email not available for signal subscription');
      return;
    }

    try {
      // Unsubscribe existing subscription if any
      if (this.signalSubscription) {
        this.signalSubscription.unsubscribe();
      }

      const subscription = stompClient.subscribe(`/user/queue/webrtc-signal`, (message: any) => {
        try {
          const signal: WebRtcSignal = JSON.parse(message.body);
          this.logger.debug('Received WebRTC signal:', signal);
          this.handleWebRtcSignal(signal);
        } catch (error) {
          this.logger.error('Error parsing WebRTC signal:', error);
        }
      });
      
      this.signalSubscription = subscription;
      this.isSubscribedToSignals = true;
      this.logger.debug('Subscribed to WebRTC signals on queue:', `/user/queue/webrtc-signal`);
    } catch (error) {
      this.logger.error('Error subscribing to WebRTC signals:', error);
      this.isSubscribedToSignals = false;
    }
  }

  private handleCallEvent(callInfo: CallInfo): void {
    this.logger.debug('Handling call event:', callInfo);
    this.logger.debug('Current user ID:', this.currentUserId);
    this.logger.debug('Call sender ID:', callInfo.senderId);
    this.logger.debug('Call receiver ID:', callInfo.receiverId);
    this.logger.debug('Is initiator:', this.isInitiator);
    
    // Replace optimistic call with real one
    if (this.currentCall?.callId?.startsWith('temp-')) {
      this.logger.debug('Replacing optimistic call with real call event');
    }
    
    // Set call start time when ringing
    if (callInfo.callStatus === 'ringing' && !this.callStartTime) {
      this.callStartTime = new Date();
      callInfo.startedAt = this.callStartTime;
      
      // Create "Call started" message in chat
      this.createCallEventMessage(callInfo, 'started');
    }
    
    this.currentCall = callInfo;
    this.callSubject.next(callInfo);

    if (callInfo.callStatus === 'ringing') {
      // Call is ringing - check if we're the sender or receiver
      const isSender = this.currentUserId && callInfo.senderId === this.currentUserId;
      const isReceiver = this.currentUserId && callInfo.receiverId === this.currentUserId;
      
      this.logger.debug('Call is ringing - isSender:', isSender, 'isReceiver:', isReceiver);
      
      if (isSender) {
        // We initiated the call - show outgoing call UI
        this.logger.debug('Showing outgoing call UI');
        // Keep isInitiator = true (already set in initiateCall)
        
        // Start timeout for unanswered call (30 seconds)
        this.startCallTimeout();
      } else if (isReceiver) {
        // We're receiving the call - show incoming call UI
        this.logger.debug('Showing incoming call UI');
        this.isInitiator = false;
        // Don't start timeout for incoming calls (only for outgoing)
      }
    } else if (callInfo.callStatus === 'accepted') {
      this.logger.debug('Call accepted');
      // Clear timeout since call was answered
      this.clearCallTimeout();
      
      if (this.isInitiator && !this.peerConnection) {
        // We initiated the call and it was accepted, start WebRTC
        this.logger.debug('Call accepted, starting WebRTC as initiator');
        this.startWebRtc();
      } else if (!this.isInitiator && !this.peerConnection) {
        // We received and accepted a call, start WebRTC
        this.logger.debug('Call accepted, starting WebRTC as receiver');
        this.startWebRtc();
      }
    } else if (callInfo.callStatus === 'ended' || callInfo.callStatus === 'rejected' || callInfo.callStatus === 'cancelled') {
      this.logger.debug('Call ended/rejected/cancelled');
      
      // Set call end time
      const endTime = new Date();
      callInfo.endedAt = endTime;
      
      // Create "Call ended" message in chat
      this.createCallEventMessage(callInfo, callInfo.callStatus === 'rejected' ? 'rejected' : 
                                          callInfo.callStatus === 'cancelled' ? 'missed' : 'ended');
      
      // Check if this was a missed call (not answered by receiver within timeout)
      const isSender = this.currentUserId && callInfo.senderId === this.currentUserId;
      const isReceiver = this.currentUserId && callInfo.receiverId === this.currentUserId;
      
      // Check for missed call (cancelled without being accepted)
      // A call is missed if:
      // 1. Status is 'cancelled' (not answered within timeout)
      // 2. We are the receiver (didn't answer)
      // 3. Call was ringing (never accepted)
      if (callInfo.callStatus === 'cancelled' && isReceiver) {
        // Receiver missed the call
        this.createMissedCallNotification(callInfo);
      }
      
      // Clear timeout since call ended
      this.clearCallTimeout();
      this.endCall();
    }
  }

  private createCallEventMessage(callInfo: CallInfo, eventType: 'started' | 'ended' | 'rejected' | 'missed'): void {
    if (!this.currentUserId) return;
    
    const isSender = this.currentUserId === callInfo.senderId;
    const otherUserId = isSender ? callInfo.receiverId : callInfo.senderId;
    const otherUserName = isSender ? callInfo.receiverName : callInfo.senderName;
    
    let messageContent = '';
    if (eventType === 'started') {
      // Only send message to the receiver (who received the call)
      // The sender knows they started the call, so no need to send them a message
      if (isSender) {
        // Current user is the sender - don't send message (they know they started the call)
        return;
      } else {
        // Current user is the receiver - send message to them
        messageContent = `📞 ${otherUserName} started a ${callInfo.callType} call`;
      }
    } else if (eventType === 'ended') {
      // Only send message once, from the sender's perspective
      // The sender initiated the call, so they send the "call ended" message
      if (isSender) {
        const duration = this.callStartTime && callInfo.endedAt 
          ? this.formatCallDuration(callInfo.endedAt.getTime() - this.callStartTime.getTime())
          : '';
        messageContent = `📞 Call ended${duration ? ` - Duration: ${duration}` : ''}`;
      } else {
        // Receiver doesn't send "call ended" message
        return;
      }
    } else if (eventType === 'rejected') {
      // For rejection: only the person who didn't reject sees the message
      // If current user rejected, don't send message (they know they rejected)
      // If other user rejected, send message to current user
      if (isSender) {
        // Current user is the caller, other user rejected - show message to current user
        messageContent = `📞 ${otherUserName} rejected the call`;
      } else {
        // Current user rejected - don't send message to chat (they know they rejected)
        return;
      }
    } else if (eventType === 'missed') {
      // Only send message to the caller (who initiated the call that was missed)
      // The receiver knows they missed the call, so no need to send them a message
      if (isSender) {
        // Current user is the caller - send message to them
        messageContent = `📞 ${otherUserName} missed the call`;
      } else {
        // Current user is the receiver who missed - don't send message (they know they missed)
        return;
      }
    }
    
    // Send as system message to chat
    if (messageContent) {
      this.chatService.sendMessage(otherUserId, messageContent);
    }
  }

  private formatCallDuration(ms: number): string {
    const seconds = Math.floor(ms / 1000);
    const minutes = Math.floor(seconds / 60);
    const remainingSeconds = seconds % 60;
    
    if (minutes > 0) {
      return `${minutes}m ${remainingSeconds}s`;
    }
    return `${seconds}s`;
  }

  private createMissedCallNotification(callInfo: CallInfo): void {
    if (!this.currentUserId) return;
    
    const isReceiver = this.currentUserId === callInfo.receiverId;
    if (!isReceiver) return; // Only create notification for receiver
    
    const senderName = callInfo.senderName;
    const callTypeLabel = callInfo.callType === 'video' ? 'video call' : 'call';
    
    this.notificationService.showNotification({
      type: 'missed-call',
      title: `Missed ${callTypeLabel}`,
      message: `You missed a ${callTypeLabel} from ${senderName}`,
      timestamp: new Date(),
      read: false,
      actionUrl: `/messages?userId=${callInfo.senderId}`
    });
  }

  private startCallTimeout(): void {
    // Clear any existing timeout
    this.clearCallTimeout();
    
    // Set timeout for 30 seconds
    this.callTimeoutId = setTimeout(() => {
      this.logger.debug('Call timeout: No answer after 30 seconds, hanging up...');
      if (this.currentCall && this.currentCall.callStatus === 'ringing') {
        // Only hang up if call is still ringing (not answered/rejected)
        // Note: Missed call notification will be created in handleCallEvent when status becomes 'cancelled'
        this.cancelCall();
      }
      this.clearCallTimeout();
    }, 30000); // 30 seconds
  }

  private clearCallTimeout(): void {
    if (this.callTimeoutId) {
      clearTimeout(this.callTimeoutId);
      this.callTimeoutId = null;
    }
  }

  private handleWebRtcSignal(signal: WebRtcSignal): void {
    this.signalSubject.next(signal);

    if (!this.peerConnection) {
      this.initializePeerConnection();
    }

    if (signal.signalType === 'offer') {
      this.handleOffer(signal.signalData);
    } else if (signal.signalType === 'answer') {
      this.handleAnswer(signal.signalData);
    } else if (signal.signalType === 'ice-candidate') {
      this.handleIceCandidate(signal.signalData);
    }
  }

  async initiateCall(receiverId: string, callType: 'video' | 'audio'): Promise<void> {
    if (!this.currentUserId) {
      await this.initCurrentUser();
    }

    // Ensure chat service is connected
    if (!this.chatService.isConnected()) {
      this.logger.debug('WebSocket not connected, attempting to connect...');
      await this.chatService.connect();
      // Wait a bit for connection to establish
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      if (!this.chatService.isConnected()) {
        throw new Error('WebSocket connection failed. Please try again.');
      }
    }

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient) {
      this.logger.error('STOMP client not available');
      throw new Error('WebSocket client not available');
    }
    
    // Verify STOMP client is activated
    if (!stompClient.active) {
      this.logger.warn('STOMP client not active, attempting to activate...');
      stompClient.activate();
      // Wait for activation
      await new Promise(resolve => setTimeout(resolve, 500));
    }
    
    this.logger.debug('STOMP client state check:', {
      active: stompClient.active,
      connected: this.chatService.isConnected(),
      clientExists: !!stompClient,
      isSubscribedToCalls: this.isSubscribedToCalls
    });

    // Ensure we're subscribed before initiating - wait for subscription to be confirmed
    if (!this.isSubscribedToCalls) {
      this.logger.debug('Setting up call subscription before initiating call...');
      this.subscribeToCalls();
      
      // Wait for subscription to be established with retries
      let retries = 0;
      while (!this.isSubscribedToCalls && retries < 10) {
        await new Promise(resolve => setTimeout(resolve, 200));
        retries++;
      }
      
      if (!this.isSubscribedToCalls) {
        this.logger.warn('Warning: Call subscription may not be ready, but proceeding with call initiation');
      } else {
        this.logger.debug('Call subscription confirmed, proceeding with call initiation');
        // Small delay to ensure subscription is fully registered with broker
        await new Promise(resolve => setTimeout(resolve, 300));
      }
    } else {
      this.logger.debug('Already subscribed to call events, ready to initiate call');
    }

    this.isInitiator = true;

    try {
      const payload = {
        receiverId: receiverId, // Send as string, backend will parse to UUID
        callType
      };
      this.logger.debug('Publishing call initiate with payload:', payload, 'STOMP client connected:', this.chatService.isConnected());
      
      // Create optimistic call state immediately
      const optimisticCall: CallInfo = {
        callId: 'temp-' + Date.now(),
        senderId: this.currentUserId || '',
        senderName: 'You',
        receiverId: receiverId,
        receiverName: 'Connecting...',
        callType: callType,
        callStatus: 'ringing'
      };
      
      // Set temporary call state to show UI immediately
      this.currentCall = optimisticCall;
      // Set call start time
      this.callStartTime = new Date();
      optimisticCall.startedAt = this.callStartTime;
      this.callSubject.next(optimisticCall);
      this.logger.debug('Set optimistic call state:', optimisticCall);
      
      // Create "Call started" message immediately for optimistic state
      this.createCallEventMessage(optimisticCall, 'started');
      
      // Start timeout for unanswered call (30 seconds)
      // This handles the optimistic call state - will be cleared if real call event is received
      this.startCallTimeout();
      
      try {
        this.logger.debug('About to publish to STOMP:', {
          destination: '/app/call.initiate',
          payload: payload,
          clientConnected: this.chatService.isConnected(),
          stompClientExists: !!stompClient
        });
        
        // Get auth token for headers
        const token = this.authService.getToken();
        const headers: any = {};
        if (token) {
          headers['Authorization'] = `Bearer ${token}`;
        }
        
        // Publish with explicit headers including Authorization
        const publishFrame = {
          destination: '/app/call.initiate',
          body: JSON.stringify(payload),
          headers: headers
        };
        
        this.logger.debug('Publishing frame:', {
          destination: publishFrame.destination,
          body: publishFrame.body,
          hasAuthHeader: !!headers['Authorization']
        });
        
        // Verify STOMP client state
        this.logger.debug('STOMP client active:', stompClient.active);
        this.logger.debug('STOMP client state:', (stompClient as any).connected);
        
        try {
          stompClient.publish(publishFrame);
          this.logger.debug('Call initiation message published successfully');
        } catch (publishError) {
          this.logger.error('Error during STOMP publish:', publishError);
          throw publishError;
        }
      } catch (error) {
        this.logger.error('Error publishing call initiation:', error);
        throw error;
      }
      
      // Replace optimistic state with real one when received, or clear after timeout
      // Increase timeout to 15 seconds to give backend more time
      const timeoutId = setTimeout(() => {
        if (this.currentCall?.callId?.startsWith('temp-')) {
          this.logger.warn('No call confirmation received after 15 seconds, clearing optimistic state');
          this.logger.warn('This might indicate a WebSocket delivery issue');
          this.logger.warn('Subscription status:', {
            isSubscribed: this.isSubscribedToCalls,
            hasSubscription: !!this.callSubscription,
            connected: this.chatService.isConnected()
          });
          // Don't clear if we received a real call event
          if (this.currentCall?.callId?.startsWith('temp-')) {
            this.currentCall = null;
            this.callSubject.next(null as any);
          }
        }
      }, 15000);
      
      // Clear timeout if we receive a real call event
      const subscription = this.call$.subscribe((callInfo: CallInfo | null) => {
        if (callInfo && !callInfo.callId?.startsWith('temp-')) {
          clearTimeout(timeoutId);
          subscription.unsubscribe();
        }
      });
    } catch (error) {
      this.logger.error('Error publishing call initiation:', error);
      this.currentCall = null;
      throw error;
    }
  }

  async answerCall(): Promise<void> {
    if (!this.currentCall) return;

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient) return;

    this.isInitiator = false;

    stompClient.publish({
      destination: '/app/call.answer',
      body: JSON.stringify(this.currentCall)
    });
  }

  async rejectCall(): Promise<void> {
    if (!this.currentCall) return;

    // Clear timeout when rejecting call
    this.clearCallTimeout();

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient) return;

    stompClient.publish({
      destination: '/app/call.reject',
      body: JSON.stringify(this.currentCall)
    });

    this.currentCall = null;
  }

  async cancelCall(): Promise<void> {
    if (!this.currentCall) return;

    // Clear timeout when cancelling call
    this.clearCallTimeout();

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient) return;

    stompClient.publish({
      destination: '/app/call.cancel',
      body: JSON.stringify(this.currentCall)
    });

    this.endCall();
  }

  async endCall(): Promise<void> {
    // Clear timeout when ending call
    this.clearCallTimeout();
    
    if (this.localStream) {
      this.localStream.getTracks().forEach(track => track.stop());
      this.localStream = null;
    }

    if (this.peerConnection) {
      this.peerConnection.close();
      this.peerConnection = null;
    }

    if (this.currentCall) {
      // Set end time if not already set
      if (!this.currentCall.endedAt) {
        this.currentCall.endedAt = new Date();
      }
      
      if (this.currentCall.callStatus === 'accepted') {
        const stompClient = (this.chatService as any).stompClient;
        if (stompClient) {
          stompClient.publish({
            destination: '/app/call.end',
            body: JSON.stringify(this.currentCall)
          });
        }
      }
    }

    // Clear call start time
    this.callStartTime = null;
    this.currentCall = null;
    this.isInitiator = false;
    this.remoteStream = null;
  }

  getCurrentCall(): CallInfo | null {
    return this.currentCall;
  }

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

  getRemoteStream(): MediaStream | null {
    return this.remoteStream;
  }

  async toggleMute(): Promise<boolean> {
    if (!this.localStream) return false;

    const audioTracks = this.localStream.getAudioTracks();
    if (audioTracks.length > 0) {
      const enabled = !audioTracks[0].enabled;
      audioTracks.forEach(track => track.enabled = enabled);
      return enabled;
    }
    return false;
  }

  async toggleVideo(): Promise<boolean> {
    if (!this.localStream) return false;

    const videoTracks = this.localStream.getVideoTracks();
    if (videoTracks.length > 0) {
      const enabled = !videoTracks[0].enabled;
      videoTracks.forEach(track => track.enabled = enabled);
      return enabled;
    }
    return false;
  }

  private async startWebRtc(): Promise<void> {
    try {
      this.logger.debug('Starting WebRTC, isInitiator:', this.isInitiator);
      await this.initializePeerConnection();
      this.logger.debug('Peer connection initialized');
      
      await this.getUserMedia();
      this.logger.debug('Media obtained successfully');
      
      if (this.isInitiator) {
        this.logger.debug('Creating offer as initiator');
        await this.createOffer();
      } else {
        this.logger.debug('Waiting for offer as receiver');
      }
    } catch (error: any) {
      this.logger.error('Error starting WebRTC:', error);
      
      // If media access fails, end the call with an error status
      if (this.currentCall && error.message) {
        this.logger.error('WebRTC failed, ending call due to error:', error.message);
        // Update call status to indicate failure
        this.currentCall.callStatus = 'ended';
        this.callSubject.next(this.currentCall);
        
        // Clean up any partial setup
        if (this.localStream) {
          this.localStream.getTracks().forEach(track => track.stop());
          this.localStream = null;
        }
        if (this.peerConnection) {
          this.peerConnection.close();
          this.peerConnection = null;
        }
        
        // Notify user via call event with error info
        const errorCallInfo = {
          ...this.currentCall,
          error: error.message || 'Failed to start video/audio call. Please check your permissions and try again.'
        };
        this.callSubject.next(errorCallInfo as any);
      }
      
      // Re-throw to allow calling code to handle if needed
      throw error;
    }
  }

  private async initializePeerConnection(): Promise<void> {
    // Wait for TURN config if not yet loaded
    if (!this.turnConfig) {
      await this.fetchTurnConfig();
    }
    
    // Build ICE servers configuration
    const iceServers: RTCIceServer[] = [];
    
    // Add Google STUN servers as fallback
    iceServers.push({ urls: 'stun:stun.l.google.com:19302' });
    iceServers.push({ urls: 'stun:stun1.l.google.com:19302' });
    
    // Add TURN server configuration if available
    if (this.turnConfig) {
      const turnServer: RTCIceServer = {
        urls: [
          `turn:${this.turnConfig.server}:${this.turnConfig.port}?transport=udp`,
          `turn:${this.turnConfig.server}:${this.turnConfig.port}?transport=tcp`
        ],
        username: this.turnConfig.username,
        credential: this.turnConfig.password
      };
      iceServers.push(turnServer);
      this.logger.debug('Using TURN server:', this.turnConfig.server + ':' + this.turnConfig.port);
    }
    
    const configuration: RTCConfiguration = {
      iceServers: iceServers,
      iceTransportPolicy: 'all' as RTCIceTransportPolicy
    };

    this.logger.debug('Peer connection ICE servers:', iceServers);
    this.peerConnection = new RTCPeerConnection(configuration);
    // Reset signaling state helpers
    this.pendingIceCandidates = [];
    this.remoteDescriptionSet = false;

    // Handle remote stream
    this.peerConnection.ontrack = (event) => {
      this.remoteStream = event.streams[0];
      this.signalSubject.next({
        callId: this.currentCall?.callId || '',
        senderId: '',
        receiverId: '',
        signalType: 'ice-candidate',
        signalData: { remoteStreamReady: true }
      });
    };

    // Handle ICE candidates
    this.peerConnection.onicecandidate = (event) => {
      if (event.candidate && this.currentCall) {
        this.sendSignal('ice-candidate', event.candidate);
      }
    };

    // Handle connection state changes
    this.peerConnection.onconnectionstatechange = () => {
      if (this.peerConnection?.connectionState === 'failed' || 
          this.peerConnection?.connectionState === 'disconnected') {
        this.logger.debug('Peer connection failed or disconnected');
      }
    };
  }

  private async getUserMedia(): Promise<void> {
    // Check if mediaDevices API is available
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      const error = new Error('Media devices API is not available. Please ensure you are using HTTPS or localhost.');
      this.logger.error('Media devices not available:', error);
      throw error;
    }

    // Check if we're in a secure context (required for getUserMedia)
    const isSecureContext = window.isSecureContext || 
                           location.protocol === 'https:' || 
                           location.hostname === 'localhost' || 
                           location.hostname === '127.0.0.1';
    
    if (!isSecureContext) {
      const error = new Error('Media access requires HTTPS or localhost.');
      this.logger.error('Not in secure context:', error);
      throw error;
    }

    const isVideoCall = this.currentCall?.callType === 'video';
    let constraints: MediaStreamConstraints = {
      audio: true,
      video: isVideoCall
    };

    try {
      this.logger.debug('Requesting media access with constraints:', constraints);
      this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
      this.logger.debug('Media access granted, stream obtained');
      
      // Add tracks to peer connection
      if (this.peerConnection && this.localStream) {
        this.localStream.getTracks().forEach(track => {
          if (this.peerConnection) {
            this.peerConnection.addTrack(track, this.localStream!);
          }
        });
        this.logger.debug('Added media tracks to peer connection');
      }
    } catch (error: any) {
      this.logger.error('Error accessing media devices:', error);
      
      // For video calls, try audio-only fallback if video fails
      if (isVideoCall && (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError' || 
                          error.message?.includes('not found'))) {
        this.logger.debug('Video access failed, attempting audio-only fallback...');
        try {
          constraints = { audio: true, video: false };
          this.logger.debug('Requesting audio-only access:', constraints);
          this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
          this.logger.debug('Audio-only access granted, continuing call without video');
          
          // Update call type to audio if we're falling back
          if (this.currentCall) {
            this.logger.debug('Falling back to audio-only call');
            // Note: The call info still says video, but we'll only have audio
          }
          
          // Add tracks to peer connection
          if (this.peerConnection && this.localStream) {
            this.localStream.getTracks().forEach(track => {
              if (this.peerConnection) {
                this.peerConnection.addTrack(track, this.localStream!);
              }
            });
            this.logger.debug('Added audio-only tracks to peer connection');
          }
          return; // Success with audio-only
        } catch (audioError: any) {
          this.logger.error('Audio-only fallback also failed:', audioError);
          // Continue to throw the original video error with better message
        }
      }
      
      // Provide user-friendly error messages
      let errorMessage = 'Failed to access media devices.';
      if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
        errorMessage = 'Camera/microphone access was denied. Please allow access in your browser settings and try again.';
      } else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError' || 
                 error.message?.includes('not found') || error.message?.includes('not found here')) {
        errorMessage = 'No camera/microphone found. Please connect a device or check your browser permissions.';
      } else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
        errorMessage = 'Camera/microphone is being used by another application. Please close other apps using the device.';
      } else if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
        errorMessage = 'Camera/microphone does not support the requested settings. Trying audio-only...';
      }
      
      this.logger.error('Detailed error info:', error, {
        name: error.name,
        message: error.message,
        constraint: error.constraint
      });
      
      // Create a more informative error
      const mediaError = new Error(errorMessage);
      (mediaError as any).originalError = error;
      throw mediaError;
    }
  }

  private async createOffer(): Promise<void> {
    if (!this.peerConnection) return;

    try {
      const offer = await this.peerConnection.createOffer();
      await this.peerConnection.setLocalDescription(offer);
      this.sendSignal('offer', offer);
    } catch (error) {
      this.logger.error('Error creating offer:', error);
    }
  }

  private async handleOffer(offer: RTCSessionDescriptionInit): Promise<void> {
    if (!this.peerConnection) return;

    try {
      await this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
      this.remoteDescriptionSet = true;
      await this.getUserMedia();
      const answer = await this.peerConnection.createAnswer();
      await this.peerConnection.setLocalDescription(answer);
      this.sendSignal('answer', answer);
      // Drain any ICE candidates that arrived before remote description
      if (this.pendingIceCandidates.length > 0) {
        for (const candidate of this.pendingIceCandidates) {
          try {
            await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
          } catch (e) {
            this.logger.error('Error applying queued ICE candidate:', e);
          }
        }
        this.pendingIceCandidates = [];
      }
    } catch (error) {
      this.logger.error('Error handling offer:', error);
    }
  }

  private async handleAnswer(answer: RTCSessionDescriptionInit): Promise<void> {
    if (!this.peerConnection) return;

    try {
      await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
    } catch (error) {
      this.logger.error('Error handling answer:', error);
    }
  }

  private async handleIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {
    if (!this.peerConnection) return;

    try {
      // If remote description not yet set, queue the candidate
      const hasRemote = !!this.peerConnection.remoteDescription;
      if (!hasRemote && !this.remoteDescriptionSet) {
        this.pendingIceCandidates.push(candidate);
        return;
      }
      await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
    } catch (error) {
      this.logger.error('Error handling ICE candidate:', error);
    }
  }

  private sendSignal(signalType: 'offer' | 'answer' | 'ice-candidate', signalData: any): void {
    if (!this.currentCall) return;

    if (!this.chatService.isConnected()) {
      this.logger.error('WebSocket not connected for sending signal');
      return;
    }

    const stompClient = (this.chatService as any).stompClient;
    if (!stompClient) {
      this.logger.error('STOMP client not available for sending signal');
      return;
    }

    const otherUserId = this.currentCall.senderId === this.currentUserId 
      ? this.currentCall.receiverId 
      : this.currentCall.senderId;

    const signal: WebRtcSignal = {
      callId: this.currentCall.callId,
      senderId: this.currentUserId || '',
      receiverId: otherUserId,
      signalType,
      signalData
    };

    try {
      stompClient.publish({
        destination: '/app/call.signal',
        body: JSON.stringify(signal)
      });
    } catch (error) {
      this.logger.error('Error sending WebRTC signal:', error);
    }
  }
}

