import type { MouseEvent } from 'react'
import {
  useCallback,
  useEffect,
  useState,
} from 'react'

import size from 'lodash/size'
import isNumber from 'lodash/isNumber'
import isEmpty from 'lodash/isEmpty'

import { isIOS } from 'config/userAgent'

import { useObjectState } from 'hooks/useObjectState'
import { useEventListener } from 'hooks/useEventListener'

import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { useLiveMatch } from 'features/MatchPage/components/LiveMatch/hooks'

import type { Chapters } from 'features/StreamPlayer/types'

import { useLexicsStore } from 'features/LexicsStore'
import { REWIND_SECONDS } from '../config'
import { useHlsPlayer } from './useHlsPlayer'
import { useFullscreen } from './useFullscreen'
import { useVideoQuality } from './useVideoQuality'
import { useControlsVisibility } from './useControlsVisibility'
import { useProgressChangeHandler } from './useProgressChangeHandler'
import { usePlayingHandlers } from './usePlayingHandlers'
import { useDuration } from './useDuration'

export type PlayerState = typeof initialState

const toMilliSeconds = (seconds: number) => seconds * 1000

const initialState = {
  activeChapterIndex: 0,
  buffering: true,
  chapters: [] as Chapters,
  duration: 0,
  loadedProgress: 0,
  playedProgress: 0,
  playing: false,
  ready: false,
  seek: 0,
  seeking: false,
}

export type Props = {
  chapters: Chapters,
  isLive: boolean,
  onDurationChange?: (duration: number) => void,
  onPlayingChange: (playing: boolean) => void,
  onProgressChange: (seconds: number) => void,
  resumeFrom?: number,
  url?: string,
}

export const useVideoPlayer = ({
  chapters: chaptersProps,
  isLive,
  onDurationChange,
  onPlayingChange,
  onProgressChange: progressChangeCallback,
  resumeFrom,
}: Props) => {
  const [{
    activeChapterIndex,
    buffering,
    chapters,
    duration: fullMatchDuration,
    loadedProgress,
    playedProgress,
    playing,
    ready,
    seek,
    seeking,
  }, setPlayerState] = useObjectState({ ...initialState, chapters: chaptersProps })

  const { onPlaylistSelect, selectedPlaylist } = useLiveMatch()

  const { lang } = useLexicsStore()

  const { url } = chapters[0] ?? { url: '' }
  const numberOfChapters = size(chapters)
  const { hls, videoRef } = useHlsPlayer(url, resumeFrom)
  const [isLiveTime, setIsLiveTime] = useState(false)
  const [isPausedTime, setIsPausedTime] = useState(false)
  const [pausedProgress, setPausedProgress] = useState(0)

  const chaptersDuration = useDuration(chapters)

  const duration = (isLive && chapters[0]?.isFullMatchChapter)
    ? fullMatchDuration
    : chaptersDuration

  const {
    onReady,
    playNextChapter,
    playPrevChapter,
    stopPlaying,
    togglePlaying,
  } = usePlayingHandlers(setPlayerState, chapters)

  const getActiveChapter = useCallback(
    (index: number = activeChapterIndex) => chapters[index],
    [chapters, activeChapterIndex],
  )

  const {
    isFullscreen,
    onFullscreenClick,
    wrapperRef,
  } = useFullscreen(videoRef)

  const [sizeOptions, setSizeOptions] = useState({
    height: wrapperRef.current?.clientHeight,
    width: wrapperRef.current?.clientWidth,
  })

  const isFirstChapterPlaying = activeChapterIndex === 0
  const isLastChapterPlaying = activeChapterIndex === numberOfChapters - 1
  const seekTo = useCallback((progressMs: number) => {
    if (!videoRef.current) return
    videoRef.current.currentTime = progressMs / 1000
  }, [videoRef])

  const rewindForward = () => {
    const chapter = getActiveChapter()
    const newProgress = playedProgress + REWIND_SECONDS * 1000
    if (newProgress <= chapter.duration || isLive) {
      seekTo(chapter.startOffsetMs + newProgress)
    } else if (isLastChapterPlaying) {
      playNextChapter()
    } else {
      const nextChapter = getActiveChapter(activeChapterIndex + 1)
      const fromMs = newProgress - chapter.duration
      playNextChapter(fromMs, nextChapter.startOffsetMs)
    }
  }

  const rewindBackward = () => {
    const chapter = getActiveChapter()
    const newProgress = playedProgress - REWIND_SECONDS * 1000
    if (newProgress >= 0) {
      seekTo(chapter.startOffsetMs + newProgress)
    } else if (isFirstChapterPlaying) {
      seekTo(chapter.startOffsetMs)
    } else {
      const prevChapter = getActiveChapter(activeChapterIndex - 1)
      const fromMs = prevChapter.duration + newProgress
      playPrevChapter(fromMs, prevChapter.startOffsetMs)
    }
  }

  const onError = useCallback(() => {
    setPlayerState({ playing: false })
  }, [setPlayerState])

  const onPlayerClick = (e: MouseEvent<HTMLDivElement>) => {
    if (e.target === videoRef.current) {
      togglePlaying()
    }
  }

  const onWaiting = () => {
    setPlayerState({ buffering: true })
  }

  const onPlaying = () => {
    setPlayerState({ buffering: false })
  }

  const onPause = () => {
    setPlayerState({ playing: false })
  }

  const onPlay = () => {
    setPlayerState({ playing: true })
  }

  const onDuration = (durationSeconds: number) => {
    setPlayerState({ duration: toMilliSeconds(durationSeconds) })
    onDurationChange?.(durationSeconds)
  }

  const onProgressChange = useProgressChangeHandler({
    chapters,
    duration,
    setPlayerState,
  })

  const onLoadedProgress = (loadedMs: number) => {
    const chapter = getActiveChapter()
    const value = loadedMs - chapter.startOffsetMs
    setPlayerState({ loadedProgress: value })
  }

  const onPlayedProgress = (playedMs: number) => {
    const chapter = getActiveChapter()
    const value = Math.max(playedMs - chapter.startOffsetMs, 0)
    setPlayerState({ playedProgress: value })

    progressChangeCallback(value / 1000)
  }

  const backToLive = useCallback(() => {
    if (!duration) return

    if (selectedPlaylist?.id !== 'full_game') {
      onPlaylistSelect({
        duration: 0,
        episodes: [],
        id: 'full_game',
        lexic: 13028,
        type: 0,
      })
      setIsLiveTime(true)
    }

    const liveProgressMs = Math.max(duration - 30000, 0)
    setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 })
    if (liveProgressMs > 0) setIsLiveTime(false)
  }, [
    duration,
    onPlaylistSelect,
    selectedPlaylist,
    setPlayerState,
  ])

  const backToPausedTime = useCallback(() => {
    if (!duration) return

    if (selectedPlaylist?.id !== 'full_game') {
      onPlaylistSelect({
        duration: 0,
        episodes: [],
        id: 'full_game',
        lexic: 13028,
        type: 0,
      })
      setIsPausedTime(true)
    }

    const liveProgressMs = Math.max(duration - 30000, 0)
    setPlayerState({ playedProgress: pausedProgress, seek: pausedProgress / 1000 })
    if (liveProgressMs > 0) setIsPausedTime(false)
    // eslint-disable-next-line
  }, [
    duration,
    onPlaylistSelect,
    selectedPlaylist,
    setPlayerState,
  ])

  useEffect(() => {
    if (chapters[0]?.isFullMatchChapter) {
      setPausedProgress(playedProgress)
    }
    // eslint-disable-next-line
  }, [selectedPlaylist])

  useEffect(() => {
    if (duration && isLiveTime && chapters[0]?.isFullMatchChapter) {
      backToLive()
    }
    // eslint-disable-next-line
  }, [duration, isLiveTime])

  useEffect(() => {
    if (duration
        && isPausedTime
        && chapters[0]?.isFullMatchChapter
    ) {
      backToPausedTime()
    }
    // eslint-disable-next-line
  }, [duration, isPausedTime])

  useEventListener({
    callback: (e: KeyboardEvent) => {
      if (e.code === 'ArrowLeft') rewindBackward()
      else if (e.code === 'ArrowRight') rewindForward()
    },
    event: 'keydown',
  })

  useEffect(() => {
    if (isNumber(seek)) {
      setPlayerState({ seek: undefined })
    }
  }, [seek, setPlayerState])

  useEffect(() => {
    onPlayingChange(playing)
    if (playing) {
      setPlayerState({ buffering: false })
    }
    // eslint-disable-next-line
  }, [playing, onPlayingChange])

  const regURL = /\d{6,20}/gi

  useEffect(() => {
    if ((isLive
      && chapters[0]?.endOffsetMs === chaptersProps[0]?.endOffsetMs
                && (
                  chapters[0]?.url.match(regURL)?.[0] === chaptersProps[0]?.url.match(regURL)?.[0]
                )
    )
      || (isEmpty(chapters) || isEmpty(chaptersProps))) return
    setPlayerState({
      ...initialState,
      chapters: chaptersProps,
      playing: true,
      seek: chaptersProps[0].startOffsetMs / 1000,
    })
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    chapters,
    chaptersProps,
    isLive,
    setPlayerState,
  ])

  useEffect(() => {
    if ((isLive && chapters[0]?.isFullMatchChapter) || isEmpty(chapters)) return

    const { duration: chapterDuration } = getActiveChapter()
    if (playedProgress >= chapterDuration && !seeking) {
      if (isLive && isLastChapterPlaying) {
        backToPausedTime()
      } else {
        playNextChapter()
      }
    }
    // eslint-disable-next-line
  }, [
    isLive,
    chapters,
    getActiveChapter,
    onPlaylistSelect,
    playedProgress,
    seeking,
    playNextChapter,
  ])

  const { isOnline } = useNoNetworkPopupStore()

  useEffect(() => {
    if (wrapperRef.current) {
      setSizeOptions({
        height: wrapperRef.current?.clientHeight,
        width: wrapperRef.current?.clientWidth,
      })
    }
  }, [wrapperRef])

  useEffect(() => {
    if (!isOnline) {
      stopPlaying()
    }
  }, [
    isOnline,
    stopPlaying,
  ])

  useEffect(() => {
    if (!navigator.serviceWorker || !isIOS) return undefined
    const listener = (event: MessageEvent) => {
      setPlayerState({ duration: toMilliSeconds(event.data.duration) })
    }

    navigator.serviceWorker.addEventListener('message', listener)
    return () => {
      navigator.serviceWorker.removeEventListener('message', listener)
    }
  }, [setPlayerState])

  useEffect(() => {
    if (ready && videoRef) {
      videoRef.current?.scrollIntoView({
        behavior: 'smooth',
        block: 'start',
      })
    }
  }, [ready, videoRef])

  const warningText = lang === 'es'
    ? 'La transmisión en vivo no está disponible temporalmente en dispositivos iOS'
    : 'Live streaming is temporarily unavailable on iOS devices'

  return {
    activeChapterIndex,
    allPlayedProgress: playedProgress + getActiveChapter().startMs,
    backToLive,
    buffering,
    chapters,
    duration,
    isFirstChapterPlaying,
    isFullscreen,
    isLastChapterPlaying,
    loadedProgress,
    numberOfChapters,
    onDuration,
    onError,
    onFullscreenClick,
    onLoadedProgress,
    onPause,
    onPlay,
    onPlayedProgress,
    onPlayerClick,
    onPlaying,
    onProgressChange,
    onReady,
    onWaiting,
    playNextChapter,
    playPrevChapter,
    playedProgress,
    playing,
    ready,
    rewindBackward,
    rewindForward,
    seek,
    sizeOptions,
    togglePlaying,
    url,
    videoRef,
    warningText,
    wrapperRef,
    ...useControlsVisibility(isFullscreen, playing),
    ...useVolume(),
    ...useVideoQuality(hls),
  }
}
