import { TRACK_KIND_AUDIO, TRACK_KIND_VIDEO } from '../../realtime.constants';
import { recordDebugNetworkStreams } from '../../logging/debug-network-metrics';
import { PeerConnectionManagerContext } from '../shared';

export function handleRemoteTrack(
  context: PeerConnectionManagerContext,
  event: RTCTrackEvent,
  remotePeerId: string
): void {
  const { logger, state } = context;
  const track = event.track;
  const isScreenAudio = isScreenShareAudioTrack(context, event, remotePeerId);
  const isScreenVideo = isScreenShareVideoTrack(context, event, remotePeerId);
  const settings =
    typeof track.getSettings === 'function' ? track.getSettings() : ({} as MediaTrackSettings);

  logger.info('Remote track', {
    remotePeerId,
    kind: track.kind,
    id: track.id,
    enabled: track.enabled,
    readyState: track.readyState,
    settings
  });

  logger.attachTrackDiagnostics(track, `remote:${remotePeerId}:${track.kind}`);

  if (track.kind === TRACK_KIND_VIDEO && (!track.enabled || track.readyState !== 'live')) {
    logger.info('Skipping inactive video track', {
      remotePeerId,
      enabled: track.enabled,
      readyState: track.readyState
    });

    return;
  }

  const compositeStream = buildCompositeRemoteStream(state, remotePeerId, track);
  const voiceStream = isVoiceAudioTrack(track, isScreenAudio)
    ? buildAudioOnlyStream(state.remotePeerVoiceStreams.get(remotePeerId), track)
    : null;
  const cameraStream = isCameraTrack(track, isScreenAudio, isScreenVideo)
    ? buildCameraStream(state.remotePeerCameraStreams.get(remotePeerId), track)
    : null;
  const screenShareStream = isScreenShareTrack(track, isScreenAudio)
    || isScreenVideo
    ? buildScreenShareStream(state.remotePeerScreenShareStreams.get(remotePeerId), track)
    : null;

  track.addEventListener('ended', () => removeRemoteTrack(context, remotePeerId, track.id));

  state.remotePeerStreams.set(remotePeerId, compositeStream);

  if (voiceStream) {
    state.remotePeerVoiceStreams.set(remotePeerId, voiceStream);
  }

  if (cameraStream) {
    state.remotePeerCameraStreams.set(remotePeerId, cameraStream);
  }

  if (screenShareStream) {
    state.remotePeerScreenShareStreams.set(remotePeerId, screenShareStream);
  }

  rememberIncomingStreamIds(state, event, remotePeerId, {
    isScreenAudio,
    isVoiceAudio: !!voiceStream,
    isCameraTrack: !!cameraStream,
    isScreenTrack: !!screenShareStream
  });

  publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
}

export function clearRemoteScreenShareStream(
  context: PeerConnectionManagerContext,
  remotePeerId: string
): void {
  const { state } = context;
  const peerData = state.activePeerConnections.get(remotePeerId);
  const screenShareStream = state.remotePeerScreenShareStreams.get(remotePeerId);

  if (!screenShareStream) {
    return;
  }

  const screenShareTrackIds = new Set(
    screenShareStream.getTracks().map((track) => track.id)
  );
  const compositeStream = removeTracksFromStreamMap(
    state.remotePeerStreams,
    remotePeerId,
    screenShareTrackIds
  );

  removeTracksFromStreamMap(state.remotePeerVoiceStreams, remotePeerId, screenShareTrackIds);
  state.remotePeerScreenShareStreams.delete(remotePeerId);

  peerData?.remoteScreenShareStreamIds.clear();

  publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
}

function buildCompositeRemoteStream(
  state: PeerConnectionManagerContext['state'],
  remotePeerId: string,
  incomingTrack: MediaStreamTrack
): MediaStream {
  return buildMergedStream(state.remotePeerStreams.get(remotePeerId), incomingTrack, {
    replaceVideoTrack: false
  });
}

function buildAudioOnlyStream(
  existingStream: MediaStream | undefined,
  incomingTrack: MediaStreamTrack
): MediaStream {
  return buildMergedStream(existingStream, incomingTrack, {
    allowedKinds: [TRACK_KIND_AUDIO],
    replaceVideoTrack: false
  });
}

function buildScreenShareStream(
  existingStream: MediaStream | undefined,
  incomingTrack: MediaStreamTrack
): MediaStream {
  return buildMergedStream(existingStream, incomingTrack, {
    replaceVideoTrack: true
  });
}

function buildCameraStream(
  existingStream: MediaStream | undefined,
  incomingTrack: MediaStreamTrack
): MediaStream {
  return buildMergedStream(existingStream, incomingTrack, {
    allowedKinds: [TRACK_KIND_VIDEO],
    replaceVideoTrack: true
  });
}

function buildMergedStream(
  existingStream: MediaStream | undefined,
  incomingTrack: MediaStreamTrack,
  options: {
    allowedKinds?: string[];
    replaceVideoTrack: boolean;
  }
): MediaStream {
  const allowedKinds = options.allowedKinds ?? [TRACK_KIND_AUDIO, TRACK_KIND_VIDEO];

  let preservedTracks: MediaStreamTrack[] = [];

  if (existingStream) {
    preservedTracks = existingStream.getTracks().filter(
      (existingTrack) => {
        if (existingTrack.readyState !== 'live') {
          return false;
        }

        if (!allowedKinds.includes(existingTrack.kind)) {
          return false;
        }

        if (options.replaceVideoTrack && incomingTrack.kind === TRACK_KIND_VIDEO) {
          return existingTrack.kind !== TRACK_KIND_VIDEO;
        }

        return existingTrack.id !== incomingTrack.id;
      }
    );
  }

  return new MediaStream([...preservedTracks, incomingTrack]);
}

function removeRemoteTrack(
  context: PeerConnectionManagerContext,
  remotePeerId: string,
  trackId: string
): void {
  const { state } = context;
  const peerData = state.activePeerConnections.get(remotePeerId);
  const compositeStream = removeTrackFromStreamMap(state.remotePeerStreams, remotePeerId, trackId);

  removeTrackFromStreamMap(state.remotePeerVoiceStreams, remotePeerId, trackId);
  removeTrackFromStreamMap(state.remotePeerCameraStreams, remotePeerId, trackId);
  removeTrackFromStreamMap(state.remotePeerScreenShareStreams, remotePeerId, trackId);

  if (!state.remotePeerVoiceStreams.has(remotePeerId)) {
    peerData?.remoteVoiceStreamIds.clear();
  }

  if (!state.remotePeerCameraStreams.has(remotePeerId)) {
    peerData?.remoteCameraStreamIds.clear();
  }

  if (!state.remotePeerScreenShareStreams.has(remotePeerId)) {
    peerData?.remoteScreenShareStreamIds.clear();
  }

  publishRemoteStreamUpdate(context, remotePeerId, compositeStream);
}

function removeTrackFromStreamMap(
  streamMap: Map<string, MediaStream>,
  remotePeerId: string,
  trackId: string
): MediaStream | null {
  return removeTracksFromStreamMap(streamMap, remotePeerId, new Set([trackId]));
}

function removeTracksFromStreamMap(
  streamMap: Map<string, MediaStream>,
  remotePeerId: string,
  trackIds: ReadonlySet<string>
): MediaStream | null {
  const currentStream = streamMap.get(remotePeerId);

  if (!currentStream) {
    return null;
  }

  const remainingTracks = currentStream
    .getTracks()
    .filter((existingTrack) => !trackIds.has(existingTrack.id) && existingTrack.readyState === 'live');

  if (remainingTracks.length === currentStream.getTracks().length) {
    return currentStream;
  }

  if (remainingTracks.length === 0) {
    streamMap.delete(remotePeerId);
    return null;
  }

  const nextStream = new MediaStream(remainingTracks);

  streamMap.set(remotePeerId, nextStream);
  return nextStream;
}

function publishRemoteStreamUpdate(
  context: PeerConnectionManagerContext,
  remotePeerId: string,
  compositeStream: MediaStream | null
): void {
  const { logger, state } = context;
  const stream = compositeStream ?? new MediaStream();

  state.remoteStream$.next({
    peerId: remotePeerId,
    stream
  });

  recordDebugNetworkStreams(remotePeerId, {
    audio: stream.getAudioTracks().length,
    video: stream.getVideoTracks().length
  });

  logger.info('Remote stream updated', {
    audioTrackCount: stream.getAudioTracks().length,
    remotePeerId,
    trackCount: stream.getTracks().length,
    videoTrackCount: stream.getVideoTracks().length
  });
}

function isVoiceAudioTrack(track: MediaStreamTrack, isScreenAudio: boolean): boolean {
  return track.kind === TRACK_KIND_AUDIO && !isScreenAudio;
}

function isCameraTrack(
  track: MediaStreamTrack,
  isScreenAudio: boolean,
  isScreenVideo: boolean
): boolean {
  return track.kind === TRACK_KIND_VIDEO && !isScreenAudio && !isScreenVideo;
}

function isScreenShareTrack(track: MediaStreamTrack, isScreenAudio: boolean): boolean {
  return track.kind === TRACK_KIND_AUDIO && isScreenAudio;
}

function isScreenShareAudioTrack(
  context: PeerConnectionManagerContext,
  event: RTCTrackEvent,
  remotePeerId: string
): boolean {
  if (event.track.kind !== TRACK_KIND_AUDIO) {
    return false;
  }

  const peerData = context.state.activePeerConnections.get(remotePeerId);

  if (!peerData) {
    return false;
  }

  const incomingStreamIds = getIncomingStreamIds(event);

  if (incomingStreamIds.some((streamId) => peerData.remoteScreenShareStreamIds.has(streamId))) {
    return true;
  }

  if (incomingStreamIds.some((streamId) => peerData.remoteVoiceStreamIds.has(streamId))) {
    return false;
  }

  if (event.streams.some((stream) => stream.getVideoTracks().some((track) => track.readyState === 'live'))) {
    return true;
  }

  const screenAudioTransceiver = peerData.connection.getTransceivers().find(
    (transceiver) => transceiver.sender === peerData.screenAudioSender
  );

  if (screenAudioTransceiver && matchesTransceiver(event.transceiver, screenAudioTransceiver)) {
    return true;
  }

  const voiceAudioTransceiver = peerData.connection.getTransceivers().find(
    (transceiver) => transceiver.sender === peerData.audioSender
  );

  if (voiceAudioTransceiver) {
    return !matchesTransceiver(event.transceiver, voiceAudioTransceiver);
  }

  const audioTransceivers = peerData.connection.getTransceivers().filter((transceiver) =>
    transceiver.receiver.track?.kind === TRACK_KIND_AUDIO || transceiver === event.transceiver
  );
  const transceiverIndex = audioTransceivers.findIndex((transceiver) =>
    transceiver === event.transceiver || (!!transceiver.mid && transceiver.mid === event.transceiver.mid)
  );

  return transceiverIndex > 0;
}

function isScreenShareVideoTrack(
  context: PeerConnectionManagerContext,
  event: RTCTrackEvent,
  remotePeerId: string
): boolean {
  if (event.track.kind !== TRACK_KIND_VIDEO) {
    return false;
  }

  const peerData = context.state.activePeerConnections.get(remotePeerId);

  if (!peerData) {
    return false;
  }

  const incomingStreamIds = getIncomingStreamIds(event);

  if (incomingStreamIds.some((streamId) => peerData.remoteScreenShareStreamIds.has(streamId))) {
    return true;
  }

  if (incomingStreamIds.some((streamId) => peerData.remoteCameraStreamIds.has(streamId))) {
    return false;
  }

  const screenVideoTransceiver = peerData.connection.getTransceivers().find(
    (transceiver) => transceiver.sender === peerData.screenVideoSender
  );

  if (screenVideoTransceiver && matchesTransceiver(event.transceiver, screenVideoTransceiver)) {
    return true;
  }

  const cameraVideoTransceiver = peerData.connection.getTransceivers().find(
    (transceiver) => transceiver.sender === peerData.videoSender
  );

  if (cameraVideoTransceiver) {
    return !matchesTransceiver(event.transceiver, cameraVideoTransceiver);
  }

  const videoTransceivers = peerData.connection.getTransceivers().filter((transceiver) =>
    transceiver.receiver.track?.kind === TRACK_KIND_VIDEO || transceiver === event.transceiver
  );
  const transceiverIndex = videoTransceivers.findIndex((transceiver) =>
    transceiver === event.transceiver || (!!transceiver.mid && transceiver.mid === event.transceiver.mid)
  );

  return transceiverIndex > 0;
}

function rememberIncomingStreamIds(
  state: PeerConnectionManagerContext['state'],
  event: RTCTrackEvent,
  remotePeerId: string,
  options: {
    isScreenAudio: boolean;
    isVoiceAudio: boolean;
    isCameraTrack: boolean;
    isScreenTrack: boolean;
  }
): void {
  const peerData = state.activePeerConnections.get(remotePeerId);

  if (!peerData) {
    return;
  }

  const incomingStreamIds = getIncomingStreamIds(event);

  if (incomingStreamIds.length === 0) {
    return;
  }

  if (options.isScreenAudio || options.isScreenTrack) {
    incomingStreamIds.forEach((streamId) => {
      peerData.remoteScreenShareStreamIds.add(streamId);
      peerData.remoteVoiceStreamIds.delete(streamId);
      peerData.remoteCameraStreamIds.delete(streamId);
    });

    return;
  }

  if (options.isCameraTrack) {
    incomingStreamIds.forEach((streamId) => {
      peerData.remoteCameraStreamIds.add(streamId);
      peerData.remoteVoiceStreamIds.delete(streamId);
      peerData.remoteScreenShareStreamIds.delete(streamId);
    });

    return;
  }

  if (options.isVoiceAudio) {
    incomingStreamIds.forEach((streamId) => {
      peerData.remoteVoiceStreamIds.add(streamId);
      peerData.remoteCameraStreamIds.delete(streamId);
      peerData.remoteScreenShareStreamIds.delete(streamId);
    });
  }
}

function getIncomingStreamIds(event: RTCTrackEvent): string[] {
  return event.streams
    .map((stream) => stream.id)
    .filter((streamId): streamId is string => !!streamId);
}

function matchesTransceiver(left: RTCRtpTransceiver, right: RTCRtpTransceiver): boolean {
  return left === right || (!!left.mid && !!right.mid && left.mid === right.mid);
}
