import { useState, useCallback, useRef, useEffect } from 'react';
import { Socket } from 'socket.io-client';
import { CallState, CallStatus, CallType, UseChatCallReturn } from '../types/chat';

interface UseChatCallProps {
  socket: Socket | null;
  currentUserId?: number;
  onCallStateChange?: (state: CallState) => void;
  getUserInfo?: (userId: number) => { name: string; avatar: string | null } | undefined;
}

const initialCallState: CallState = {
  callId: null,
  status: 'idle',
  type: 'audio',
  isIncoming: false,
  remoteUserId: null,
  remoteUserName: null,
  remoteUserAvatar: null,
  startTime: null,
  localStream: null,
  remoteStream: null,
  isMuted: false,
  isVideoOff: false,
  isSpeakerOn: true,
};

// WebRTC configuration
const rtcConfig: RTCConfiguration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'stun:stun1.l.google.com:19302' },
  ],
};

export const useChatCall = ({
  socket,
  currentUserId,
  onCallStateChange,
  getUserInfo,
}: UseChatCallProps): UseChatCallReturn => {
  const [callState, setCallState] = useState<CallState>(initialCallState);

  const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
  const localStreamRef = useRef<MediaStream | null>(null);
  const callTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const callIdRef = useRef<string | null>(null);

  // Keep callIdRef in sync with callState.callId
  useEffect(() => {
    callIdRef.current = callState.callId;
  }, [callState.callId]);

  // Update state helper
  const updateCallState = useCallback((updates: Partial<CallState>) => {
    setCallState((prev) => {
      const newState = { ...prev, ...updates };
      onCallStateChange?.(newState);
      return newState;
    });
  }, [onCallStateChange]);

  // Reset call state
  const resetCallState = useCallback(() => {
    // Clean up media streams
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach((track) => track.stop());
      localStreamRef.current = null;
    }

    // Close peer connection
    if (peerConnectionRef.current) {
      peerConnectionRef.current.close();
      peerConnectionRef.current = null;
    }

    // Clear timeout
    if (callTimeoutRef.current) {
      clearTimeout(callTimeoutRef.current);
      callTimeoutRef.current = null;
    }

    setCallState(initialCallState);
  }, []);

  // Get user media (audio/video)
  const getUserMedia = useCallback(async (type: CallType): Promise<MediaStream | null> => {
    try {
      const constraints: MediaStreamConstraints = {
        audio: true,
        video: type === 'video',
      };
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      localStreamRef.current = stream;
      return stream;
    } catch (err) {
      console.error('[Call] Failed to get user media:', err);
      return null;
    }
  }, []);

  // Create peer connection
  const createPeerConnection = useCallback(() => {
    const pc = new RTCPeerConnection(rtcConfig);

    // Add local tracks to peer connection
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach((track) => {
        pc.addTrack(track, localStreamRef.current!);
      });
    }

    // Handle incoming tracks (remote stream)
    pc.ontrack = (event) => {
      console.log('[Call] Remote track received');
      const remoteStream = event.streams[0];
      updateCallState({ remoteStream });
    };

    // Handle ICE candidates - use ref to get current callId (avoids stale closure)
    pc.onicecandidate = (event) => {
      if (event.candidate && socket && callIdRef.current) {
        console.log('[Call] Sending ICE candidate for call:', callIdRef.current);
        socket.emit('call:ice-candidate', {
          callId: callIdRef.current,
          candidate: event.candidate.candidate,
          sdpMid: event.candidate.sdpMid,
          sdpMLineIndex: event.candidate.sdpMLineIndex,
        });
      }
    };

    // Handle connection state changes
    pc.onconnectionstatechange = () => {
      console.log('[Call] Connection state:', pc.connectionState);
      if (pc.connectionState === 'connected') {
        updateCallState({ status: 'connected', startTime: new Date() });
      } else if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') {
        updateCallState({ status: 'failed' });
      }
    };

    peerConnectionRef.current = pc;
    return pc;
  }, [socket, updateCallState]);

  // Start a call
  const startCall = useCallback(async (userId: number, type: CallType) => {
    if (!socket || callState.status !== 'idle') {
      console.error('[Call] Cannot start call: socket not connected or call in progress');
      return;
    }

    // Get user info for display
    const userInfo = getUserInfo?.(userId);

    // Update state to calling
    updateCallState({
      status: 'calling',
      type,
      isIncoming: false,
      remoteUserId: userId,
      remoteUserName: userInfo?.name || `User ${userId}`,
      remoteUserAvatar: userInfo?.avatar || null,
    });

    // Get local media
    const localStream = await getUserMedia(type);
    if (!localStream) {
      updateCallState({ status: 'failed' });
      return;
    }
    updateCallState({ localStream });

    // Create peer connection
    createPeerConnection();

    // Send call offer
    socket.emit('call:offer', {
      calleeExternalId: String(userId),
      type,
    }, (response: { success: boolean; callId?: string; error?: string }) => {
      if (response.success && response.callId) {
        console.log('[Call] Call offer sent, callId:', response.callId);
        updateCallState({ callId: response.callId, status: 'ringing' });

        // Set timeout for no answer (60 seconds)
        callTimeoutRef.current = setTimeout(() => {
          if (callState.status === 'ringing') {
            console.log('[Call] Call timeout - no answer');
            endCall();
          }
        }, 60000);
      } else {
        console.error('[Call] Failed to start call:', response.error);
        updateCallState({ status: 'failed' });
        resetCallState();
      }
    });
  }, [socket, callState.status, getUserInfo, getUserMedia, createPeerConnection, updateCallState, resetCallState]);

  // Accept incoming call
  const acceptCall = useCallback(async () => {
    if (!socket || !callState.callId || callState.status !== 'ringing') {
      console.error('[Call] Cannot accept call: invalid state');
      return;
    }

    // Get local media
    const localStream = await getUserMedia(callState.type);
    if (!localStream) {
      declineCall();
      return;
    }
    updateCallState({ localStream, status: 'connecting' });

    // Create peer connection
    createPeerConnection();

    // Accept the call
    socket.emit('call:accept', { callId: callState.callId }, (response: { success: boolean; error?: string }) => {
      if (response.success) {
        console.log('[Call] Call accepted');
      } else {
        console.error('[Call] Failed to accept call:', response.error);
        updateCallState({ status: 'failed' });
      }
    });
  }, [socket, callState.callId, callState.status, callState.type, getUserMedia, createPeerConnection, updateCallState]);

  // Decline incoming call
  const declineCall = useCallback(() => {
    if (!socket || !callState.callId) {
      resetCallState();
      return;
    }

    socket.emit('call:decline', { callId: callState.callId });
    resetCallState();
  }, [socket, callState.callId, resetCallState]);

  // End active call
  const endCall = useCallback(() => {
    if (!socket) {
      resetCallState();
      return;
    }

    if (callState.callId) {
      // If call was in ringing state and we're the caller, cancel it
      if (callState.status === 'ringing' && !callState.isIncoming) {
        socket.emit('call:cancel', { callId: callState.callId });
      } else {
        socket.emit('call:end', { callId: callState.callId });
      }
    }

    resetCallState();
  }, [socket, callState.callId, callState.status, callState.isIncoming, resetCallState]);

  // Toggle mute
  const toggleMute = useCallback(() => {
    if (localStreamRef.current) {
      const audioTrack = localStreamRef.current.getAudioTracks()[0];
      if (audioTrack) {
        audioTrack.enabled = !audioTrack.enabled;
        updateCallState({ isMuted: !audioTrack.enabled });
      }
    }
  }, [updateCallState]);

  // Toggle video
  const toggleVideo = useCallback(() => {
    if (localStreamRef.current) {
      const videoTrack = localStreamRef.current.getVideoTracks()[0];
      if (videoTrack) {
        videoTrack.enabled = !videoTrack.enabled;
        updateCallState({ isVideoOff: !videoTrack.enabled });
      }
    }
  }, [updateCallState]);

  // Toggle speaker (for mobile - not fully implemented here)
  const toggleSpeaker = useCallback(() => {
    updateCallState({ isSpeakerOn: !callState.isSpeakerOn });
  }, [callState.isSpeakerOn, updateCallState]);

  // Handle incoming call
  const handleIncomingCall = useCallback((data: {
    callId: string;
    callerExternalId: string;
    callerName?: string;
    callerAvatar?: string;
    type?: CallType;
  }) => {
    console.log('[Call] Incoming call:', data);

    if (callState.status !== 'idle') {
      // Already in a call, decline
      socket?.emit('call:decline', { callId: data.callId, reason: 'busy' });
      return;
    }

    const callerId = parseInt(data.callerExternalId, 10);
    const userInfo = getUserInfo?.(callerId);

    updateCallState({
      callId: data.callId,
      status: 'ringing',
      type: data.type || 'audio',
      isIncoming: true,
      remoteUserId: callerId,
      remoteUserName: data.callerName || userInfo?.name || `User ${callerId}`,
      remoteUserAvatar: data.callerAvatar || userInfo?.avatar || null,
    });
  }, [socket, callState.status, getUserInfo, updateCallState]);

  // Handle call accepted (both parties receive this)
  const handleCallAccepted = useCallback(async (data: { callId: string }) => {
    console.log('[Call] Call accepted:', data.callId);

    // Clear timeout
    if (callTimeoutRef.current) {
      clearTimeout(callTimeoutRef.current);
      callTimeoutRef.current = null;
    }

    updateCallState({ status: 'connecting' });

    // If we're the caller, create and send SDP offer
    if (!callState.isIncoming && peerConnectionRef.current) {
      try {
        const offer = await peerConnectionRef.current.createOffer();
        await peerConnectionRef.current.setLocalDescription(offer);

        socket?.emit('call:sdp', {
          callId: data.callId, // Use callId from event data, not stale state
          type: 'offer',
          sdp: offer.sdp,
        });
      } catch (err) {
        console.error('[Call] Failed to create offer:', err);
        updateCallState({ status: 'failed' });
      }
    }
  }, [socket, callState.isIncoming, updateCallState]);

  // Handle SDP exchange
  const handleSDP = useCallback(async (data: { callId: string; type: 'offer' | 'answer'; sdp: string }) => {
    console.log('[Call] Received SDP:', data.type);

    if (!peerConnectionRef.current) {
      console.error('[Call] No peer connection');
      return;
    }

    try {
      await peerConnectionRef.current.setRemoteDescription(
        new RTCSessionDescription({ type: data.type, sdp: data.sdp })
      );

      // If we received an offer, create and send answer
      if (data.type === 'offer') {
        const answer = await peerConnectionRef.current.createAnswer();
        await peerConnectionRef.current.setLocalDescription(answer);

        socket?.emit('call:sdp', {
          callId: data.callId, // Use callId from event data, not stale state
          type: 'answer',
          sdp: answer.sdp,
        });
      }
    } catch (err) {
      console.error('[Call] Failed to handle SDP:', err);
    }
  }, [socket]);

  // Handle ICE candidate
  const handleICECandidate = useCallback(async (data: {
    callId: string;
    candidate: string;
    sdpMid: string | null;
    sdpMLineIndex: number | null;
  }) => {
    if (!peerConnectionRef.current) return;

    try {
      await peerConnectionRef.current.addIceCandidate(
        new RTCIceCandidate({
          candidate: data.candidate,
          sdpMid: data.sdpMid,
          sdpMLineIndex: data.sdpMLineIndex,
        })
      );
    } catch (err) {
      console.error('[Call] Failed to add ICE candidate:', err);
    }
  }, []);

  // Handle call declined
  const handleCallDeclined = useCallback((data: { callId: string; reason?: string }) => {
    console.log('[Call] Call declined:', data);
    updateCallState({ status: data.reason === 'busy' ? 'busy' : 'declined' });
    setTimeout(resetCallState, 2000);
  }, [updateCallState, resetCallState]);

  // Handle call cancelled
  const handleCallCancelled = useCallback((data: { callId: string }) => {
    console.log('[Call] Call cancelled:', data);
    updateCallState({ status: 'cancelled' });
    setTimeout(resetCallState, 2000);
  }, [updateCallState, resetCallState]);

  // Handle call timeout
  const handleCallTimeout = useCallback((data: { callId: string }) => {
    console.log('[Call] Call timeout:', data);
    updateCallState({ status: 'timeout' });
    setTimeout(resetCallState, 2000);
  }, [updateCallState, resetCallState]);

  // Handle call ended
  const handleCallEnded = useCallback((data: { callId: string }) => {
    console.log('[Call] Call ended:', data);
    updateCallState({ status: 'ended' });
    setTimeout(resetCallState, 1000);
  }, [updateCallState, resetCallState]);

  // Handle call answered on another device/session
  const handleCallAnsweredElsewhere = useCallback((data: { callId: string; answeredBy: string }) => {
    console.log('[Call] Call answered on another device:', data);

    // Only process if this is for our current incoming call
    if (callState.callId === data.callId && callState.isIncoming) {
      // Stop ringing and dismiss incoming call UI
      resetCallState();
    }
  }, [callState.callId, callState.isIncoming, resetCallState]);

  // Set up socket event listeners
  useEffect(() => {
    if (!socket) return;

    socket.on('call:incoming', handleIncomingCall);
    socket.on('call:accepted', handleCallAccepted);
    socket.on('call:declined', handleCallDeclined);
    socket.on('call:cancelled', handleCallCancelled);
    socket.on('call:timeout', handleCallTimeout);
    socket.on('call:ended', handleCallEnded);
    socket.on('call:answered_elsewhere', handleCallAnsweredElsewhere);
    socket.on('call:sdp', handleSDP);
    socket.on('call:ice-candidate', handleICECandidate);

    return () => {
      socket.off('call:incoming', handleIncomingCall);
      socket.off('call:accepted', handleCallAccepted);
      socket.off('call:declined', handleCallDeclined);
      socket.off('call:cancelled', handleCallCancelled);
      socket.off('call:timeout', handleCallTimeout);
      socket.off('call:ended', handleCallEnded);
      socket.off('call:answered_elsewhere', handleCallAnsweredElsewhere);
      socket.off('call:sdp', handleSDP);
      socket.off('call:ice-candidate', handleICECandidate);
    };
  }, [
    socket,
    handleIncomingCall,
    handleCallAccepted,
    handleCallDeclined,
    handleCallCancelled,
    handleCallTimeout,
    handleCallEnded,
    handleCallAnsweredElsewhere,
    handleSDP,
    handleICECandidate,
  ]);

  // Clean up on unmount
  useEffect(() => {
    return () => {
      resetCallState();
    };
  }, [resetCallState]);

  return {
    callState,
    startCall,
    acceptCall,
    declineCall,
    endCall,
    toggleMute,
    toggleVideo,
    toggleSpeaker,
  };
};

export default useChatCall;
