import { useCallback, useEffect, useRef, useState } from 'react';

import { useCallStateHooks } from '@stream-io/video-react-sdk';
import { type AxiosResponse } from 'axios';
import { getUnixTime } from 'date-fns';
import screenfull from 'screenfull';
import videojs from 'video.js';

import 'video.js/dist/video-js.css';

import {
  IconPlayerPause,
  IconPlayerPlay,
  IconPlayerSkipBack,
  IconPlayerTrackNext,
  IconPlayerTrackPrev,
} from '@material-hu/icons/tabler';
import Chip from '@material-hu/mui/Chip';
import IconButton from '@material-hu/mui/IconButton';
import Modal from '@material-hu/mui/Modal';
import Slider from '@material-hu/mui/Slider';
import Stack from '@material-hu/mui/Stack';
import { alpha, useTheme } from '@material-hu/mui/styles';
import Typography from '@material-hu/mui/Typography';

import {
  FullScreenElementType,
  type LiveStream,
  type LiveStreamPost,
  SourceType,
} from 'src/types/stream';
import { formatVideoTimer } from 'src/utils/attachments';
import { useLokaliseTranslation } from 'src/utils/i18n';

import { FULL_SCREEN_COMMENT_WIDTH } from '../constants';
import { calculatePillsTopMargin, getIsRtmpConnection, isLive } from '../utils';

import ClosePlayerButton from './components/ClosePlayerButton';
import Controls from './components/Controls';
import LiveDetails, { type ReactionsHandlers } from './components/LiveDetails';
import ParticipantCountPill from './components/ParticipantCountPill';
import Poll from './components/Poll';
import useScreenDimensions from './hooks/useScreenDimensions';
import useShowControls from './hooks/useShowControls';
import useVolume from './hooks/useVolume';
import { Background } from '.';

type VideoJsPlayer = ReturnType<typeof videojs> & {
  liveTracker: {
    liveWindow: () => number;
  };
};

const TIME_PREPARING_HLS_URL_IN_SECONDS = 20;

type Props = {
  stream: LiveStream;
  post: LiveStreamPost;
  reactionsHandlers: ReactionsHandlers;
  pollService?: {
    vote: (pollId: number, pollOptionId: number) => Promise<AxiosResponse>;
    key: Array<string | number>;
  };
};

export const HlsVideoPlayer = (props: Props) => {
  const { stream, post, reactionsHandlers, pollService } = props;
  const videoRef = useRef<HTMLDivElement | null>(null);
  const playerRef = useRef<VideoJsPlayer | null>(null);
  const fullScrenNodeRef = useRef<HTMLDivElement | null>(null);

  const [sliderValue, setSliderValue] = useState(100);
  const [isDataLoaded, setIsDataLoaded] = useState(false);
  const [isPreparingHlsUrl, setIsPreparingHlsUrl] = useState(true);
  const [fullScreenElement, setFullScreenElement] =
    useState<FullScreenElementType>(FullScreenElementType.NONE);

  const speakerUpdate = useCallback((newValue: number) => {
    playerRef.current?.volume(newValue);
  }, []);

  const volumeHandlers = useVolume(0, speakerUpdate);

  const { containerDimensions } = useScreenDimensions(
    fullScreenElement !== FullScreenElementType.NONE,
    fullScrenNodeRef.current,
  );

  const { handleMouseMove, showControls } = useShowControls();

  const theme = useTheme();
  const { t } = useLokaliseTranslation('livestream');
  const { useParticipantCount, useCallCustomData } = useCallStateHooks();
  const { isRtmpConnection: customDataIsRtmpConnection } = useCallCustomData();
  const participantCount = useParticipantCount();
  const isRtmpConnection = getIsRtmpConnection(
    stream.streamSource,
    customDataIsRtmpConnection,
  );

  const currentPlayerInstance = playerRef.current;
  const isPlaying = currentPlayerInstance
    ? !currentPlayerInstance.paused()
    : false;
  const currentTime = currentPlayerInstance?.currentTime();
  const isWatchingLive = isLive(
    currentPlayerInstance?.currentTime(),
    currentPlayerInstance?.liveTracker.liveWindow(),
  );

  const realParticipantCount = isRtmpConnection
    ? participantCount - 2
    : participantCount - 1;
  const isStartingTransmission = !stream.hlsUrl || isPreparingHlsUrl;

  useEffect(() => {
    let intervalId: ReturnType<typeof setInterval> | undefined;

    if (videoRef.current && !playerRef.current && !isStartingTransmission) {
      const videoElement = document.createElement('video-js');

      videoRef.current.appendChild(videoElement);

      const newPlayer = videojs(
        videoElement,
        {
          autoplay: true,
          controls: false,
          responsive: true,
          fluid: true,
          liveTracker: true,
          sources: [
            {
              src: stream.hlsUrl,
              type: 'application/x-mpegURL',
            },
          ],
        },
        () => {
          newPlayer.on('timeupdate', () => {
            const liveWindow = newPlayer.liveTracker.liveWindow();
            const current = newPlayer.currentTime() ?? 0;
            const percentage =
              liveWindow !== 0 ? (current / liveWindow) * 100 : 100;

            setSliderValue(percentage);
          });

          newPlayer.on('loadeddata', () => {
            setIsDataLoaded(true);
            clearInterval(intervalId);
          });
        },
      ) as VideoJsPlayer;

      playerRef.current = newPlayer;

      intervalId = setInterval(() => {
        if (isDataLoaded) {
          clearInterval(intervalId);
        }

        if (playerRef.current && !isDataLoaded) {
          playerRef.current.load();
        }
      }, 3000);
    }

    return () => {
      if (playerRef.current && !playerRef.current.isDisposed()) {
        playerRef.current.dispose();
        playerRef.current = null;
      }
      clearInterval(intervalId);
    };
  }, [
    stream.hlsUrl,
    isStartingTransmission,
    fullScreenElement,
    videoRef.current,
  ]);

  useEffect(() => {
    let timeoutId: ReturnType<typeof setTimeout> | undefined;
    const streamStartedAtTimestamp = getUnixTime(new Date(stream.startedAt));
    const currentTimestamp = getUnixTime(new Date());
    const timeStampDifference = currentTimestamp - streamStartedAtTimestamp;

    if (timeStampDifference < TIME_PREPARING_HLS_URL_IN_SECONDS) {
      timeoutId = setTimeout(
        () => {
          setIsPreparingHlsUrl(false);
        },
        (TIME_PREPARING_HLS_URL_IN_SECONDS - timeStampDifference) * 1000,
      );
    } else {
      setIsPreparingHlsUrl(false);
    }

    return () => clearTimeout(timeoutId);
  }, []);

  useEffect(() => {
    const handleFullscreenChange = () => {
      setFullScreenElement(
        document.fullscreenElement === fullScrenNodeRef.current
          ? FullScreenElementType.FULL
          : FullScreenElementType.SEMI,
      );
    };

    window.addEventListener('fullscreenchange', handleFullscreenChange);

    return () => {
      window.removeEventListener('fullscreenchange', handleFullscreenChange);
    };
  }, []);

  const handlePlay = () => {
    if (!playerRef.current) return;
    playerRef.current.play();
  };

  const handlePause = () => {
    if (!playerRef.current) return;
    playerRef.current.pause();
  };

  const togglePlayPause = () => {
    isPlaying ? handlePause() : handlePlay();
  };

  const handleGoLive = () => {
    const currentPlayer = playerRef.current;
    if (!currentPlayer) return;
    currentPlayer.currentTime(currentPlayer.liveTracker.liveWindow());
  };

  const handleReset = () => {
    if (!playerRef.current) return;
    playerRef.current.currentTime(0);
  };

  const handleForward = () => {
    if (!playerRef.current) return;
    playerRef.current.currentTime((playerRef.current.currentTime() ?? 0) + 10);
  };

  const handleRewind = () => {
    if (!playerRef.current) return;
    playerRef.current.currentTime((playerRef.current.currentTime() ?? 0) - 10);
  };

  const toggleFullScreen = () => {
    if (fullScreenElement === FullScreenElementType.FULL) {
      setFullScreenElement(FullScreenElementType.SEMI);
      screenfull.toggle(fullScrenNodeRef.current ?? undefined);
    }

    if (fullScreenElement === FullScreenElementType.SEMI) {
      screenfull.toggle(fullScrenNodeRef.current ?? undefined);
      setFullScreenElement(FullScreenElementType.FULL);
    }

    if (fullScreenElement === FullScreenElementType.NONE) {
      setFullScreenElement(FullScreenElementType.SEMI);
    }
  };

  const handleExitFullscreen = () => {
    setFullScreenElement(FullScreenElementType.NONE);
  };

  const handleSeekChange = (_event: Event, newValue: number | number[]) => {
    if (typeof newValue !== 'number') return;
    const liveWindow = playerRef.current?.liveTracker.liveWindow();
    if (liveWindow) {
      const newTime = (newValue / 100) * liveWindow;
      playerRef.current?.currentTime(newTime);
    }
  };

  if (isStartingTransmission) {
    return (
      <Background>
        <Typography color={theme.palette.new.text.neutral.inverted}>
          {t('STARTING_TRANSMISSION')}
        </Typography>
      </Background>
    );
  }

  const player = (
    <Stack
      data-vjs-player
      sx={{
        position: 'relative',
        height: '100%',
      }}
      ref={fullScrenNodeRef}
      onMouseMove={handleMouseMove}
    >
      <Stack
        sx={{
          position: 'absolute',
          top:
            fullScreenElement === FullScreenElementType.SEMI
              ? theme.spacing(2)
              : calculatePillsTopMargin({
                  spacing: 16,
                  fullScreenElement,
                  participantViewElement: fullScrenNodeRef.current,
                  containerDimensions,
                  source: stream.source,
                  // for hls lives we don't know if there is a screenshare ongoing in that moment of the video or not.
                  // so we always show the pills in the same position. same for hasVideo
                  hasOngoingScreenshare: false,
                  hasVideo: true,
                }),
          left: theme.spacing(2),
          flexDirection: 'row',
          alignItems: 'center',
          gap: theme.spacing(1),
          zIndex: 1,
        }}
      >
        {fullScreenElement !== FullScreenElementType.NONE && (
          <ClosePlayerButton onClick={handleExitFullscreen} />
        )}
        {realParticipantCount > 0 && (
          <ParticipantCountPill count={realParticipantCount} />
        )}
      </Stack>
      <Stack
        ref={videoRef}
        sx={
          fullScreenElement === FullScreenElementType.NONE
            ? {
                '& .video-js.vjs-fluid:not(.vjs-audio-only-mode)': {
                  maxHeight: '620px',
                  paddingTop:
                    stream.source === SourceType.MOBILE ? '56.25%' : undefined,
                  borderRadius: 2,

                  '& .vjs-tech': {
                    borderRadius: 2,
                  },
                },
              }
            : {
                height: '100%',
                '& .video-js.vjs-fluid:not(.vjs-audio-only-mode)': {
                  height: '100%',
                  paddingTop:
                    stream.source === SourceType.MOBILE ? '56.25%' : undefined,

                  '& .vjs-tech': {
                    objectFit: 'contain',
                    width:
                      fullScreenElement === FullScreenElementType.SEMI
                        ? `calc(100% - ${FULL_SCREEN_COMMENT_WIDTH})`
                        : '100%',
                  },
                },
              }
        }
      />
      {post.poll && pollService && (
        <Poll
          poll={post.poll}
          pollService={pollService}
          fullScreenElement={fullScreenElement}
          toggleFullscreen={toggleFullScreen}
          isHls
        />
      )}
      <Controls
        fullScreenElement={fullScreenElement}
        showControls={showControls}
        controlHandlers={{ toggleFullscreen: toggleFullScreen }}
        volumeHandlers={volumeHandlers}
      >
        {!isWatchingLive && (
          <IconButton
            sx={{
              svg: { stroke: theme.palette.base?.white },
              '&:hover svg': {
                stroke: theme.palette.base?.white,
              },
            }}
            onClick={handleReset}
          >
            <IconPlayerSkipBack />
          </IconButton>
        )}
        {!isWatchingLive && (
          <IconButton
            onClick={handleRewind}
            sx={{
              svg: { stroke: theme.palette.base?.white },
              '&:hover svg': {
                stroke: theme.palette.base?.white,
              },
            }}
          >
            <IconPlayerTrackPrev />
          </IconButton>
        )}
        <IconButton
          onClick={togglePlayPause}
          sx={{
            svg: { stroke: theme.palette.base?.white },
            '&:hover svg': {
              stroke: theme.palette.base?.white,
            },
          }}
        >
          {isPlaying && <IconPlayerPause />}
          {!isPlaying && <IconPlayerPlay />}
        </IconButton>
        {!isWatchingLive && (
          <IconButton
            onClick={handleForward}
            sx={{
              svg: { stroke: theme.palette.base?.white },
              '&:hover svg': {
                stroke: theme.palette.base?.white,
              },
            }}
          >
            <IconPlayerTrackNext />
          </IconButton>
        )}
        <Typography
          variant="globalXS"
          color={theme.palette.new.text.neutral.inverted}
          sx={{
            minWidth: '50px',
            ml: theme.spacing(1),
          }}
        >
          {formatVideoTimer(currentTime ?? 0)}
        </Typography>
        <Chip
          // DS doesn't have dark variant yet
          sx={{
            height: '25px',
            mr: theme.spacing(2),
            backgroundColor: isWatchingLive
              ? theme.palette.base?.red[800]
              : alpha(theme.palette.base?.grey[900] ?? '#000000', 0.4),
            border: `1px solid ${isWatchingLive ? theme.palette.base?.red[600] : alpha(theme.palette.base?.grey[900] ?? '#000000', 0.4)}`,
            color: isWatchingLive
              ? theme.palette.base?.white
              : theme.palette.base?.grey[100],
            fontSize: 12,
            fontWeight: 600,
          }}
          label={t('LIVE')}
          onClick={isWatchingLive ? undefined : handleGoLive}
        />
        <Slider
          min={0}
          max={100}
          value={sliderValue}
          onChange={handleSeekChange}
          disabled={!playerRef.current?.liveTracker.liveWindow()}
          sx={{
            '& .MuiSlider-thumb': {
              height: 12,
              width: 12,
              backgroundColor: theme.palette.new.background.layout.tertiary,
              display: 'none',
              border: 'none',
            },
            '& .MuiSlider-track': {
              height: 4,
              backgroundColor: isWatchingLive
                ? theme.palette.base?.red[400]
                : theme.palette.base?.blueBrand[400],
              borderColor: isWatchingLive
                ? theme.palette.base?.red[400]
                : theme.palette.base?.blueBrand[400],
            },
            '& .MuiSlider-rail': {
              height: 4,
              backgroundColor: theme.palette.border?.neutralDivider,
            },
            '&:hover': {
              '& .MuiSlider-thumb': {
                display: 'block',
                boxShadow: `0px 0px 0px 8px ${isWatchingLive ? alpha(theme.palette.base?.red[400] ?? '#000000', 0.15) : alpha(theme.palette.base?.blueBrand[400] ?? '#000000', 0.15)}`,
              },
            },
          }}
        />
      </Controls>
      {fullScreenElement === FullScreenElementType.SEMI && (
        <LiveDetails
          post={post}
          reactionsHandlers={reactionsHandlers}
          fullScreenContainer={fullScrenNodeRef.current}
        />
      )}
    </Stack>
  );

  return (
    <>
      {fullScreenElement === FullScreenElementType.NONE && player}
      <Modal
        open={fullScreenElement !== FullScreenElementType.NONE}
        onClose={() => setFullScreenElement(FullScreenElementType.NONE)}
      >
        <Stack
          sx={{
            position: 'absolute',
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            width: '100vw',
            height: '100vh',
            bgcolor: 'new.background.layout.default',
          }}
        >
          {player}
        </Stack>
      </Modal>
    </>
  );
};

export default HlsVideoPlayer;
