import {
  CONNECTION_STATE_CLOSED,
  CONNECTION_STATE_CONNECTED,
  CONNECTION_STATE_DISCONNECTED,
  CONNECTION_STATE_FAILED,
  DATA_CHANNEL_LABEL,
  SIGNALING_TYPE_ICE_CANDIDATE,
  TRACK_KIND_AUDIO,
  TRACK_KIND_VIDEO,
  TRANSCEIVER_RECV_ONLY,
  TRANSCEIVER_SEND_RECV
} from '../../realtime.constants';
import { recordDebugNetworkConnectionState } from '../../logging/debug-network-metrics';
import { PeerData } from '../../realtime.types';
import { ConnectionLifecycleHandlers, PeerConnectionManagerContext } from '../shared';

/**
 * Create and configure a new RTCPeerConnection for a remote peer.
 */
export function createPeerConnection(
  context: PeerConnectionManagerContext,
  remotePeerId: string,
  isInitiator: boolean,
  handlers: ConnectionLifecycleHandlers
): PeerData {
  const { callbacks, logger, state } = context;

  logger.info('Creating peer connection', { remotePeerId, isInitiator });

  const connection = new RTCPeerConnection({ iceServers: callbacks.getIceServers() });

  let dataChannel: RTCDataChannel | null = null;
  let peerData: PeerData | null = null;

  const adoptDataChannel = (channel: RTCDataChannel): void => {
    const primaryChannel = dataChannel;
    const shouldAdoptAsPrimary = !primaryChannel || primaryChannel.readyState === 'closed';

    if (shouldAdoptAsPrimary) {
      dataChannel = channel;

      if (peerData) {
        peerData.dataChannel = channel;
      }

      const existing = state.activePeerConnections.get(remotePeerId);

      if (existing) {
        existing.dataChannel = channel;
      }
    } else if (primaryChannel !== channel) {
      logger.info('Received secondary data channel while primary channel is still active', {
        channelLabel: channel.label,
        primaryChannelLabel: primaryChannel.label,
        primaryReadyState: primaryChannel.readyState,
        remotePeerId
      });
    }

    handlers.setupDataChannel(channel, remotePeerId);
  };

  connection.onicecandidate = (event) => {
    if (event.candidate) {
      if (!callbacks.isSignalingConnected()) {
        return;
      }

      logger.info('ICE candidate gathered', {
        remotePeerId,
        candidateType: (event.candidate as RTCIceCandidate & { type?: string }).type
      });

      callbacks.sendRawMessage({
        type: SIGNALING_TYPE_ICE_CANDIDATE,
        targetUserId: remotePeerId,
        payload: { candidate: event.candidate }
      });
    }
  };

  connection.onconnectionstatechange = () => {
    logger.info('connectionstatechange', {
      remotePeerId,
      state: connection.connectionState
    });

    // Ignore events from a connection that was already replaced in the Map
    // (e.g. handleOffer recreated the peer while this handler was still queued).
    const currentPeer = state.activePeerConnections.get(remotePeerId);

    if (currentPeer && currentPeer.connection !== connection) {
      logger.info('Ignoring stale connectionstatechange', {
        remotePeerId,
        state: connection.connectionState
      });

      return;
    }

    recordDebugNetworkConnectionState(remotePeerId, connection.connectionState);

    switch (connection.connectionState) {
      case CONNECTION_STATE_CONNECTED:
        handlers.clearPeerDisconnectGraceTimer(remotePeerId);
        handlers.addToConnectedPeers(remotePeerId);
        state.peerConnected$.next(remotePeerId);
        handlers.clearPeerReconnectTimer(remotePeerId);
        state.disconnectedPeerTracker.delete(remotePeerId);
        handlers.requestVoiceStateFromPeer(remotePeerId);
        break;

      case CONNECTION_STATE_DISCONNECTED:
        handlers.schedulePeerDisconnectRecovery(remotePeerId);
        break;

      case CONNECTION_STATE_FAILED:
        handlers.trackDisconnectedPeer(remotePeerId);
        handlers.removePeer(remotePeerId, { preserveReconnectState: true });
        handlers.schedulePeerReconnect(remotePeerId);
        break;

      case CONNECTION_STATE_CLOSED:
        handlers.removePeer(remotePeerId);
        break;
    }
  };

  connection.oniceconnectionstatechange = () => {
    logger.info('iceconnectionstatechange', {
      remotePeerId,
      state: connection.iceConnectionState
    });
  };

  connection.onsignalingstatechange = () => {
    logger.info('signalingstatechange', {
      remotePeerId,
      state: connection.signalingState
    });
  };

  connection.onnegotiationneeded = () => {
    logger.info('negotiationneeded', { remotePeerId });
  };

  connection.ontrack = (event) => {
    handlers.handleRemoteTrack(event, remotePeerId);
  };

  connection.ondatachannel = (event) => {
    logger.info('Received data channel', { remotePeerId });
    adoptDataChannel(event.channel);
  };

  if (isInitiator) {
    dataChannel = connection.createDataChannel(DATA_CHANNEL_LABEL, { ordered: true });
    handlers.setupDataChannel(dataChannel, remotePeerId);
  }

  peerData = {
    connection,
    dataChannel,
    createdAt: Date.now(),
    isInitiator,
    pendingIceCandidates: [],
    audioSender: undefined,
    videoSender: undefined,
    remoteVoiceStreamIds: new Set<string>(),
    remoteCameraStreamIds: new Set<string>(),
    remoteScreenShareStreamIds: new Set<string>()
  };

  if (isInitiator) {
    const audioTransceiver = connection.addTransceiver(TRACK_KIND_AUDIO, {
      direction: TRANSCEIVER_SEND_RECV
    });
    const videoTransceiver = connection.addTransceiver(TRACK_KIND_VIDEO, {
      direction: TRANSCEIVER_RECV_ONLY
    });

    peerData.audioSender = audioTransceiver.sender;
    peerData.videoSender = videoTransceiver.sender;
  }

  state.activePeerConnections.set(remotePeerId, peerData);

  const localStream = callbacks.getLocalMediaStream();

  if (localStream && isInitiator) {
    logger.logStream(`localStream->${remotePeerId}`, localStream);

    localStream.getTracks().forEach((track) => {
      if (track.kind === TRACK_KIND_AUDIO && peerData.audioSender) {
        if (typeof peerData.audioSender.setStreams === 'function') {
          peerData.audioSender.setStreams(localStream);
        }

        peerData.audioSender
          .replaceTrack(track)
          .then(() => logger.info('audio replaceTrack (init) ok', { remotePeerId }))
          .catch((error) =>
            logger.error('audio replaceTrack failed at createPeerConnection', error)
          );
      } else if (track.kind === TRACK_KIND_VIDEO && peerData.videoSender) {
        if (typeof peerData.videoSender.setStreams === 'function') {
          peerData.videoSender.setStreams(localStream);
        }

        peerData.videoSender
          .replaceTrack(track)
          .then(() => logger.info('video replaceTrack (init) ok', { remotePeerId }))
          .catch((error) =>
            logger.error('video replaceTrack failed at createPeerConnection', error)
          );
      } else {
        const sender = connection.addTrack(track, localStream);

        if (track.kind === TRACK_KIND_AUDIO)
          peerData.audioSender = sender;

        if (track.kind === TRACK_KIND_VIDEO)
          peerData.videoSender = sender;
      }
    });
  }

  return peerData;
}
