import { useEffect, useMemo, useRef, useState } from 'react';
import { useFormContext } from 'react-hook-form';

import { useCall, useCallStateHooks } from '@stream-io/video-react-sdk';

import {
  type BytesSample,
  type RateHistories,
  UserTypeStream,
  VideoOrigin,
} from 'src/types/stream';

import {
  computeBitrateBps,
  formatBitrate,
  formatCodec,
  formatFrameRate,
  formatResolution,
  getPublisherSnapshot,
  getRtmpSnapshot,
  getTimeTicks,
  pushMetricSample,
  toRateMetric,
} from '../utils';

export const useStreamMetrics = (isStreaming: boolean) => {
  const call = useCall();
  const { useCallStatsReport, useParticipants } = useCallStateHooks();
  const statsReport = useCallStatsReport();
  const participants = useParticipants();
  const { watch } = useFormContext();

  const videoOrigin: VideoOrigin = watch('videoOrigin');
  const isSoftwareStreaming = videoOrigin === VideoOrigin.SOFTWARE_STREAMING;
  const rtmpSessionId = isSoftwareStreaming
    ? participants.find(participant =>
        participant.userId.split('-').includes(UserTypeStream.RTMP),
      )?.sessionId
    : undefined;

  const previousVideoSample = useRef<BytesSample>();
  const previousAudioSample = useRef<BytesSample>();
  const sessionStartTimestamp = useRef<number>();
  const liveStartElapsedSeconds = useRef<number>();
  const [histories, setHistories] = useState<RateHistories>({
    videoBitrate: [],
    frameRate: [],
    audioBitrate: [],
  });
  const [sampleTimes, setSampleTimes] = useState<number[]>([]);

  useEffect(() => {
    if (!call || !rtmpSessionId) return;

    call.startReportingStatsFor(rtmpSessionId);

    return () => {
      call.stopReportingStatsFor(rtmpSessionId);
    };
  }, [call, rtmpSessionId]);

  // biome-ignore lint/correctness/useExhaustiveDependencies: the deps are the trigger, not inputs — accumulated samples are stale whenever the source changes (webcam ↔ RTMP, or a new RTMP session)
  useEffect(() => {
    previousVideoSample.current = undefined;
    previousAudioSample.current = undefined;
    sessionStartTimestamp.current = undefined;
    liveStartElapsedSeconds.current = undefined;
    setHistories({
      videoBitrate: [],
      frameRate: [],
      audioBitrate: [],
    });
    setSampleTimes([]);
  }, [isSoftwareStreaming, rtmpSessionId]);

  const snapshot = useMemo(() => {
    if (!statsReport) return null;
    if (!isSoftwareStreaming) return getPublisherSnapshot(statsReport);

    return rtmpSessionId ? getRtmpSnapshot(statsReport, rtmpSessionId) : null;
  }, [statsReport, isSoftwareStreaming, rtmpSessionId]);

  // biome-ignore lint/correctness/useExhaustiveDependencies: each snapshot must be pushed exactly once — re-running on `isStreaming` would duplicate the sample; the live start is captured on the next snapshot (≤2s)
  useEffect(() => {
    if (!snapshot) return;

    const videoSample: BytesSample = {
      totalBytes: snapshot.videoBytes,
      timestamp: snapshot.timestamp,
    };
    const audioSample: BytesSample = {
      totalBytes: snapshot.audioBytes,
      timestamp: snapshot.timestamp,
    };

    const videoBitrate = computeBitrateBps(
      previousVideoSample.current,
      videoSample,
    );
    const audioBitrate = computeBitrateBps(
      previousAudioSample.current,
      audioSample,
    );

    previousVideoSample.current = videoSample;
    previousAudioSample.current = audioSample;
    sessionStartTimestamp.current ??= snapshot.timestamp;

    const elapsedSeconds =
      (snapshot.timestamp - sessionStartTimestamp.current) / 1000;
    if (isStreaming) liveStartElapsedSeconds.current ??= elapsedSeconds;

    setSampleTimes(current => pushMetricSample(current, elapsedSeconds));

    const { frameRate } = snapshot;

    setHistories(current => ({
      videoBitrate:
        videoBitrate !== null
          ? pushMetricSample(current.videoBitrate, videoBitrate)
          : current.videoBitrate,
      frameRate:
        frameRate !== undefined
          ? pushMetricSample(current.frameRate, frameRate)
          : current.frameRate,
      audioBitrate:
        audioBitrate !== null
          ? pushMetricSample(current.audioBitrate, audioBitrate)
          : current.audioBitrate,
    }));
  }, [snapshot]);

  const timeTicks = getTimeTicks(sampleTimes, liveStartElapsedSeconds.current);

  return {
    ready:
      histories.videoBitrate.length > 0 && histories.audioBitrate.length > 0,
    videoBitrate: toRateMetric(histories.videoBitrate, formatBitrate),
    frameRate: toRateMetric(histories.frameRate, formatFrameRate),
    audioBitrate: toRateMetric(histories.audioBitrate, formatBitrate),
    timeTicks,
    maxVideoResolution: formatResolution(snapshot?.frameHeight),
    audioFormat: formatCodec(snapshot?.audioCodec),
  };
};
