import { FC, useRef, useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useInView } from 'react-intersection-observer';
import { useQuery } from 'react-query';

import screenful from 'screenfull';

import Box from '@material-hu/mui/Box';
import CardMedia from '@material-hu/mui/CardMedia';
import CircularProgress from '@material-hu/mui/CircularProgress';

import useSnackbar from '@material-hu/components/design-system/Snackbar';

import { logEvent } from 'src/config/logging';
import { MAX_HEIGHTS, MAX_HEIGHTS_POSTS } from 'src/constants/attachments';
import { fetchCaptions } from 'src/services/attachments';
import { EventName } from 'src/types/amplitude';
import {
  getImageDimensions,
  getVideoElapsedTime,
  isPlaying,
} from 'src/utils/attachments';
import { downloadUrl } from 'src/utils/files';

import Captions from './components/Captions';
import Controls from './Controls';
import { attachmentKeys } from './queries';

export type VideoProps = {
  media: any;
  onLoad?: () => void;
  mediaHeight?: number;
  mediaWidth?: number;
  isChat?: boolean;
  isLoading?: boolean;
  defaultMuted?: boolean;
  grantDownload?: boolean;
};

const Video: FC<VideoProps> = props => {
  const {
    media,
    onLoad = () => null,
    mediaHeight,
    mediaWidth,
    isChat = false,
    isLoading = false,
    defaultMuted = true,
    grantDownload = false,
  } = props;

  const { name, url, width, height, source, thumbnailUrl, videoCaptionUrl } =
    media;

  const playerContainerRef = useRef<HTMLDivElement>(null);
  const controlsRef = useRef<HTMLDivElement>(null);

  const { ref: setVideoRef, inView } = useInView({
    threshold: 0.6,
  });
  const videoRef = useRef<HTMLVideoElement>(null);

  const setRef = useCallback(
    (node: HTMLVideoElement | null) => {
      if (node) (videoRef as any).current = node;

      setVideoRef(node);
    },
    [setVideoRef],
  );

  const [playing, setPlaying] = useState(true);
  const [volume, setVolume] = useState(defaultMuted ? 0 : 1);
  const [muted, setMuted] = useState(defaultMuted);
  const [playbackRate, setPlaybackRate] = useState(1);
  const [duration, setDuration] = useState(0.0);
  const [blockSeek, setBlockSeek] = useState(false);
  const [playedTime, setPlayedTime] = useState(0.0);
  const [isPictureInPicture, setIsPictureInPicture] = useState(false);
  const [isVideoEnded, setIsVideoEnded] = useState(false);
  const [isCaptionsOpen, setIsCaptionsOpen] = useState(false);
  const [currentCaptions, setCurrentCaptions] = useState<string | null>(null);

  const { enqueueSnackbar } = useSnackbar();
  const { t } = useTranslation('attachments');

  const { data: captions } = useQuery(
    attachmentKeys.captions(videoCaptionUrl),
    () => fetchCaptions(videoCaptionUrl),
    {
      onError: () => {
        enqueueSnackbar({
          title: t('error_fetching_captions'),
          variant: 'error',
        });
      },
      enabled: !!videoCaptionUrl,
    },
  );

  useEffect(() => {
    const autoplay = async () => {
      const video = videoRef?.current;

      if (!video || isLoading) return;

      if (inView) {
        if (defaultMuted) {
          mute(true);
          video.volume = 0;
        }
        play();
      } else if (isPlaying(video)) {
        pause();
      }
    };

    autoplay();
  }, [inView, videoRef?.current, isLoading, defaultMuted]);

  useEffect(() => {
    const video = videoRef?.current;

    const handleTimeUpdate = () => {
      if (!video) return;
      if (!blockSeek) {
        setPlayedTime((video.currentTime * 100) / video.duration);
      }
      if (duration === 0) {
        setDuration(video.duration);
      }
    };

    video?.addEventListener('timeupdate', handleTimeUpdate);

    return () => {
      video?.removeEventListener('timeupdate', handleTimeUpdate);
    };
  }, [videoRef?.current]);

  useEffect(() => {
    let requestAnimationId: number;
    const updateSubtitle = () => {
      if (videoRef.current) {
        const currentTime = videoRef.current.currentTime;
        const activeSubtitle = captions?.find(
          subtitle =>
            currentTime >= subtitle.startTime &&
            currentTime <= subtitle.endTime,
        );
        setCurrentCaptions(activeSubtitle ? activeSubtitle.text : null);
      }
      requestAnimationId = requestAnimationFrame(updateSubtitle);
    };

    if (isCaptionsOpen) {
      requestAnimationId = requestAnimationFrame(updateSubtitle);
    }

    return () => {
      cancelAnimationFrame(requestAnimationId);
    };
  }, [captions, videoRef.current, isCaptionsOpen]);

  const play = async () => {
    const video = videoRef?.current;
    if (!video) return;

    try {
      await video.play();
      setPlaying(true);
      setIsVideoEnded(false);
    } catch (error) {
      setPlaying(false);
    }
  };

  const pause = () => {
    const video = videoRef?.current;
    if (!video) return;

    video.pause();
    setPlaying(false);
    setIsVideoEnded(false);
  };

  const handlePlayPause = () => {
    if (!videoRef?.current) {
      return;
    }
    return isPlaying(videoRef.current) ? pause() : play();
  };

  const handleSeekChange = (_e: any, newValue: number) => {
    setPlayedTime(newValue);
    if (videoRef.current)
      videoRef.current.currentTime = (newValue * duration) / 100;
  };

  const handleSeekMouseDown = () => setBlockSeek(true);

  const mute = (newMuted: boolean) => {
    const video = videoRef?.current;
    if (!video) return;

    video.muted = newMuted;
    setMuted(newMuted);
  };

  const handleVolumeChange = (_e: any, newValue: number) => {
    const video = videoRef?.current;
    if (!video) return;

    setVolume(newValue / 100);

    video.volume = newValue / 100;
    mute(newValue <= 0);
  };

  const handleMute = () => {
    const video = videoRef?.current;
    if (!video) return;

    if (!muted) {
      video.volume = 0;
    } else if (volume === 0) {
      video.volume = 1;
      setVolume(1);
    } else {
      video.volume = volume;
    }

    mute(!muted);
  };

  const toggleFullScreen = () => {
    if (!playerContainerRef.current) return;
    screenful.toggle(playerContainerRef.current);
  };

  const handleMouseMove = () => {
    if (controlsRef?.current) {
      controlsRef.current.style.visibility = 'visible';
    }
  };

  const hanldeMouseLeave = () => {
    if (controlsRef?.current) {
      controlsRef.current.style.visibility = 'hidden';
    }
  };

  const handlePlaybackRate = (rate: number) => {
    const video = videoRef?.current;
    if (!video) return;

    video.playbackRate = rate;
    setPlaybackRate(rate);
  };

  const handlePictureInPicture = () => {
    if (document.pictureInPictureElement) {
      document.exitPictureInPicture();
      setIsPictureInPicture(false);
    } else {
      videoRef.current?.requestPictureInPicture();
      setIsPictureInPicture(true);
    }
  };

  const [isDownloading, setIsDownloading] = useState(false);

  const handleDownload = async () => {
    setIsDownloading(true);
    try {
      await downloadUrl(url, name);
    } finally {
      setIsDownloading(false);
    }
  };

  const handleVideoEnded = () => setIsVideoEnded(true);

  const { imageWidth, imageHeight } = getImageDimensions({
    mediaWidth,
    mediaHeight,
    width,
    height,
  });

  const getVideoWidth = () => {
    if (isChat) {
      return screenful.isFullscreen ? 'auto' : mediaWidth;
    }
    if (hasDimensions) {
      return screenful.isFullscreen ? 'auto' : imageWidth;
    }
    return width || 'auto';
  };

  const getVideoHeight = () => {
    if (isChat) {
      return screenful.isFullscreen ? 'auto' : mediaHeight;
    }
    if (hasDimensions) {
      return screenful.isFullscreen ? 'auto' : imageHeight;
    }
    return height || 'auto';
  };

  const getVideoMaxHeight = () => {
    if (hasDimensions) {
      return screenful.isFullscreen
        ? 'calc(100vh - 40px)'
        : MAX_HEIGHTS_POSTS.VIDEO;
    }
    return MAX_HEIGHTS.VIDEO;
  };

  const toggleCaptions = () => {
    setIsCaptionsOpen(!isCaptionsOpen);
  };

  const elapsedTime = getVideoElapsedTime(videoRef?.current);
  const hasDimensions = mediaWidth && mediaHeight;

  return (
    <Box
      sx={{
        px: 0,
        width: !hasDimensions ? 'unset' : '100%',
      }}
    >
      <Box
        id={url}
        onMouseMove={handleMouseMove}
        onMouseLeave={hanldeMouseLeave}
        ref={playerContainerRef}
        sx={{
          width: '100%',
          position: 'relative',
          height: '100%',
          backgroundColor: '#000000',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          borderRadius: isChat ? '8px' : '0px',
        }}
      >
        {isLoading && (
          <Box
            sx={{
              position: 'absolute',
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
            }}
          >
            <CircularProgress />
          </Box>
        )}
        <CardMedia
          src={source || url}
          ref={setRef}
          component="video"
          preload="metadata"
          playsInline
          poster={thumbnailUrl}
          width={getVideoWidth()}
          height={getVideoHeight()}
          onLoad={onLoad}
          muted={muted}
          controls={!hasDimensions && !isLoading}
          onEnded={handleVideoEnded}
          onClick={hasDimensions ? handlePlayPause : undefined}
          sx={{
            backgroundPosition: 'top',
            maxHeight: getVideoMaxHeight(),
            maxWidth: 'calc(100vw - 40px)',
            objectFit: 'contain',
          }}
          onError={e => {
            if (e.currentTarget.error?.code) {
              logEvent(EventName.VIDEO_ERROR, {
                errorMessage: e.currentTarget.error.message,
                errorCode: e.currentTarget.error.code,
                url,
              });
            }
          }}
        />
        {hasDimensions && !isLoading && (
          <Controls
            ref={controlsRef}
            idContainer={screenful.isFullscreen ? url : undefined}
            onSeek={handleSeekChange}
            onSeekMouseDown={handleSeekMouseDown}
            onSeekMouseUp={handleSeekChange}
            onVolumeChange={handleVolumeChange}
            onVolumeSeekDown={handleVolumeChange}
            onPlayPause={handlePlayPause}
            onDownload={handleDownload}
            onPictureInPicture={handlePictureInPicture}
            playing={playing}
            played={playedTime}
            elapsedTime={elapsedTime}
            onMute={handleMute}
            muted={muted}
            playbackRate={playbackRate}
            onPlaybackRateChange={handlePlaybackRate}
            onToggleFullScreen={toggleFullScreen}
            fullScreenValue={screenful.isFullscreen}
            volume={volume}
            isPictureInPicture={isPictureInPicture}
            isVideoEnded={isVideoEnded}
            isChat={isChat}
            captionsEnabled={videoCaptionUrl}
            onToggleCaptions={toggleCaptions}
            isCaptionsOpen={isCaptionsOpen}
            grantDownload={grantDownload}
            isDownloading={isDownloading}
          />
        )}
        {videoCaptionUrl && isCaptionsOpen && (
          <Captions
            captions={currentCaptions}
            variant={screenful.isFullscreen ? 'globalL' : 'globalXXS'}
          />
        )}
      </Box>
    </Box>
  );
};

export default Video;
