import React, { useEffect, useCallback, useState } from 'react'
import { usePlayerContext } from '../contexts/PlayerContext'
import type { SubtitleTrack, AudioTrack, VideoQuality, VideoProtocol } from '../types'
import { validateVideoURL, getCORSErrorMessage, isCORSError } from '../utils/corsHelper'
import { setHlsAudioTrack, setHlsQualityLevel } from '../utils/hlsControl'
import { setupHlsInstance } from '../utils/hlsSetup'
import { setupRtmpInstance } from '../utils/rtmpSetup'
import { setupMpegtsInstance } from '../utils/mpegtsSetup'
import { detectVideoProtocol } from '../utils/videoProtocol'
import { createSubtitleBlobURL } from '../utils/subtitles'
import { logger } from '../utils/logger'
import './VideoElement.css'

interface VideoElementProps {
  src: string
  poster?: string
  protocol?: 'auto' | VideoProtocol
  autoplay?: boolean
  loop?: boolean
  muted?: boolean
  volume?: number
  playbackRate?: number
  currentTime?: number
  crossOrigin?: '' | 'anonymous' | 'use-credentials'
  preload?: 'none' | 'metadata' | 'auto'
  playsInline?: boolean
  controlsList?: string
  subtitles?: SubtitleTrack[]
  onPlay?: () => void
  onPause?: () => void
  onEnded?: () => void
  onTimeUpdate?: (currentTime: number) => void
  onVolumeChange?: (volume: number) => void
  onError?: (error: Error) => void
  onLoadedMetadata?: () => void
  onSeeking?: () => void
  onSeeked?: () => void
  onProgress?: (buffered: number) => void
  onDurationChange?: (duration: number) => void
  onRateChange?: (playbackRate: number) => void
  onFullscreenChange?: (isFullscreen: boolean) => void
  onPictureInPictureChange?: (isPictureInPicture: boolean) => void
  onWaiting?: () => void
  onCanPlay?: () => void
  onAudioTracksLoaded?: (tracks: AudioTrack[]) => void
  onQualityLevelsLoaded?: (qualities: VideoQuality[]) => void
  onSubtitleTracksLoaded?: (tracks: SubtitleTrack[]) => void
}

export const VideoElement: React.FC<VideoElementProps> = ({
  src,
  poster,
  protocol = 'auto',
  autoplay = false,
  loop = false,
  muted = false,
  volume,
  playbackRate,
  currentTime: initialCurrentTime,
  crossOrigin,
  preload = 'metadata',
  playsInline = true,
  controlsList,
  subtitles = [],
  onPlay,
  onPause,
  onEnded,
  onTimeUpdate,
  onVolumeChange,
  onError,
  onLoadedMetadata,
  onSeeking,
  onSeeked,
  onProgress,
  onDurationChange,
  onRateChange,
  onFullscreenChange,
  onPictureInPictureChange,
  onWaiting,
  onCanPlay,
  onAudioTracksLoaded,
  onQualityLevelsLoaded,
  onSubtitleTracksLoaded,
}) => {
  const { videoRef, setVideoState, toggleFullscreen, settings, setQuality, setSubtitle } = usePlayerContext()
  const lastClickTimeRef = React.useRef<number>(0)
  const [availableAudioTracks, setAvailableAudioTracks] = useState<AudioTrack[]>([])
  const [availableQualities, setAvailableQualities] = useState<VideoQuality[]>([])
  const [hlsSubtitles, setHlsSubtitles] = useState<SubtitleTrack[]>([])
  const [processedSubtitles, setProcessedSubtitles] = useState<SubtitleTrack[]>([])
  const subtitleBlobUrlsRef = React.useRef<string[]>([])

  // Handle video events
  const handlePlay = useCallback(() => {
    setVideoState((prev) => ({ ...prev, playing: true }))
    onPlay?.()
  }, [setVideoState, onPlay])

  const handlePause = useCallback(() => {
    setVideoState((prev) => ({ ...prev, playing: false }))
    onPause?.()
  }, [setVideoState, onPause])

  const handleTimeUpdate = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    const currentTime = video.currentTime
    const buffered = video.buffered.length > 0 ? video.buffered.end(video.buffered.length - 1) : 0

    setVideoState((prev) => ({
      ...prev,
      currentTime,
      buffered,
    }))

    onTimeUpdate?.(currentTime)
  }, [videoRef, setVideoState, onTimeUpdate])

  const handleLoadedMetadata = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    // Check if this is a live broadcast (duration is Infinity for live streams)
    const isLiveBroadcast = !isFinite(video.duration) || video.duration === 0
    logger.log('[VideoElement] Is live broadcast?', isLiveBroadcast, 'duration:', video.duration)

    setVideoState((prev) => ({
      ...prev,
      duration: video.duration,
      volume: video.volume,
      muted: video.muted,
      isLiveBroadcast,
    }))

    // Enable default subtitle if specified
    const tracks = video.textTracks
    if (tracks && processedSubtitles.length > 0) {
      const defaultSubtitle = processedSubtitles.find((sub) => sub.default)
      if (defaultSubtitle) {
        logger.log(`🎯 Found default subtitle in metadata: ${defaultSubtitle.label}`)
        // Set subtitle in context (this will trigger the useEffect that enables it)
        setSubtitle(defaultSubtitle)
      }
    }

    onLoadedMetadata?.()
  }, [videoRef, setVideoState, onLoadedMetadata, processedSubtitles, setSubtitle])

  const handleDurationChange = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    // Re-check if this is a live broadcast when duration changes
    const isLiveBroadcast = !isFinite(video.duration) || video.duration === 0
    logger.log('[VideoElement] Duration changed. Is live broadcast?', isLiveBroadcast, 'duration:', video.duration)

    setVideoState((prev) => ({
      ...prev,
      duration: video.duration,
      isLiveBroadcast,
    }))

    onDurationChange?.(video.duration)
  }, [videoRef, setVideoState, onDurationChange])

  const handleVolumeChange = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    setVideoState((prev) => ({
      ...prev,
      volume: video.volume,
      muted: video.muted,
    }))

    onVolumeChange?.(video.volume)
  }, [videoRef, setVideoState, onVolumeChange])

  const handleSeeking = useCallback(() => {
    setVideoState((prev) => ({ ...prev, seeking: true }))
    onSeeking?.()
  }, [setVideoState, onSeeking])

  const handleSeeked = useCallback(() => {
    setVideoState((prev) => ({ ...prev, seeking: false }))
    onSeeked?.()
  }, [setVideoState, onSeeked])

  const handleWaiting = useCallback(() => {
    setVideoState((prev) => ({ ...prev, loading: true }))
    onWaiting?.()
  }, [setVideoState, onWaiting])

  const handleCanPlay = useCallback(() => {
    setVideoState((prev) => ({ ...prev, loading: false }))
    onCanPlay?.()
  }, [setVideoState, onCanPlay])

  const handleProgress = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    const buffered = video.buffered.length > 0 ? video.buffered.end(video.buffered.length - 1) : 0
    onProgress?.(buffered)
  }, [videoRef, onProgress])

  const handleRateChange = useCallback(() => {
    const video = videoRef.current
    if (!video) return

    setVideoState((prev) => ({ ...prev, playbackRate: video.playbackRate }))
    onRateChange?.(video.playbackRate)
  }, [videoRef, setVideoState, onRateChange])

  const handleEnded = useCallback(() => {
    setVideoState((prev) => ({ ...prev, playing: false }))
    onEnded?.()
  }, [setVideoState, onEnded])

  const handleError = useCallback(() => {
    const video = videoRef.current
    if (!video || !video.error) return

    let errorMessage = `Video error: ${video.error.message}`

    // Check if it's a CORS-related error
    const videoError = video.error
    if (
      videoError.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ||
      videoError.code === MediaError.MEDIA_ERR_NETWORK
    ) {
      // Could be a CORS issue
      errorMessage = getCORSErrorMessage(video.src || src)
    }

    const error = new Error(errorMessage)
    setVideoState((prev) => ({ ...prev, error, loading: false }))
    onError?.(error)
  }, [videoRef, setVideoState, onError, src])

  // Handle double-click on video for fullscreen toggle
  const handleVideoClick = useCallback(
    (e: React.MouseEvent<HTMLVideoElement>) => {
      const now = Date.now()
      const timeSinceLastClick = now - lastClickTimeRef.current

      if (timeSinceLastClick < 300) {
        // Double click - toggle fullscreen
        e.preventDefault()
        toggleFullscreen()
        lastClickTimeRef.current = 0
      } else {
        // Single click - record time
        lastClickTimeRef.current = now
      }
    },
    [toggleFullscreen]
  )

  // Handle fullscreen changes
  useEffect(() => {
    const handleFullscreenChange = () => {
      const isFullscreen = !!document.fullscreenElement
      setVideoState((prev) => ({ ...prev, fullscreen: isFullscreen }))
      onFullscreenChange?.(isFullscreen)
    }

    document.addEventListener('fullscreenchange', handleFullscreenChange)
    return () => {
      document.removeEventListener('fullscreenchange', handleFullscreenChange)
    }
  }, [setVideoState, onFullscreenChange])

  // Handle PIP changes
  useEffect(() => {
    const handlePIPChange = () => {
      const isPIP = !!document.pictureInPictureElement
      setVideoState((prev) => ({ ...prev, pictureInPicture: isPIP }))
      onPictureInPictureChange?.(isPIP)
    }

    document.addEventListener('enterpictureinpicture', handlePIPChange)
    document.addEventListener('leavepictureinpicture', handlePIPChange)

    return () => {
      document.removeEventListener('enterpictureinpicture', handlePIPChange)
      document.removeEventListener('leavepictureinpicture', handlePIPChange)
    }
  }, [setVideoState, onPictureInPictureChange])

  // Apply volume prop to video element
  useEffect(() => {
    const video = videoRef.current
    if (!video || volume === undefined) return

    // Clamp volume between 0 and 1
    const clampedVolume = Math.max(0, Math.min(1, volume))
    if (video.volume !== clampedVolume) {
      video.volume = clampedVolume
    }
  }, [volume, videoRef])

  // Apply playbackRate prop to video element
  useEffect(() => {
    const video = videoRef.current
    if (!video || playbackRate === undefined) return

    if (video.playbackRate !== playbackRate) {
      video.playbackRate = playbackRate
    }
  }, [playbackRate, videoRef])

  // Apply currentTime prop to video element (only once on mount or when it changes)
  useEffect(() => {
    const video = videoRef.current
    if (!video || initialCurrentTime === undefined) return

    // Only seek if the difference is significant (more than 1 second)
    if (Math.abs(video.currentTime - initialCurrentTime) > 1) {
      video.currentTime = initialCurrentTime
    }
  }, [initialCurrentTime, videoRef])

  // Process subtitles - convert SRT to VTT blob URLs and merge with HLS subtitles
  useEffect(() => {
    let cancelled = false

    // Clean up old blob URLs
    subtitleBlobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url))
    subtitleBlobUrlsRef.current = []

    // Merge manual subtitles and HLS subtitles
    const allSubtitles = [...subtitles, ...hlsSubtitles]

    if (allSubtitles.length === 0) {
      setProcessedSubtitles([])
      return
    }

    const processSubtitles = async () => {
      const processed = await Promise.all(
        allSubtitles.map(async (subtitle) => {
          try {
            // Check if it's an SRT file
            if (subtitle.src.endsWith('.srt')) {
              // Fetch and convert SRT to VTT
              const response = await fetch(subtitle.src)
              if (!response.ok) {
                throw new Error(`Failed to fetch subtitle: ${response.status} ${response.statusText}`)
              }
              const srtContent = await response.text()

              const blobUrl = createSubtitleBlobURL(srtContent, 'srt')
              subtitleBlobUrlsRef.current.push(blobUrl)

              return { ...subtitle, src: blobUrl }
            }
            // VTT files can be used directly
            return subtitle
          } catch (error) {
            logger.error(`Failed to process subtitle ${subtitle.label}:`, error)
            return subtitle
          }
        })
      )

      // Only update state if not cancelled
      if (!cancelled) {
        setProcessedSubtitles(processed)
      }
    }

    processSubtitles()

    // Cleanup function
    return () => {
      cancelled = true
      subtitleBlobUrlsRef.current.forEach((url) => URL.revokeObjectURL(url))
      subtitleBlobUrlsRef.current = []
    }
  }, [subtitles, hlsSubtitles])

  useEffect(() => {
    const video = videoRef.current
    if (!video) return
    if (processedSubtitles.length === 0) return
    if (settings.subtitle) return

    const defaultSubtitle = processedSubtitles.find((subtitle) => subtitle.default)
    if (!defaultSubtitle) return

    const tracks = video.textTracks
    if (!tracks || tracks.length === 0) return

    for (let i = 0; i < tracks.length; i++) {
      const track = tracks[i]
      if (track.language === defaultSubtitle.lang) {
        track.mode = 'showing'
        setSubtitle(defaultSubtitle)
        break
      }
    }
  }, [processedSubtitles, settings.subtitle, setSubtitle, videoRef])

  // Detect video protocol and setup appropriate player
  useEffect(() => {
    const video = videoRef.current
    if (!video) return
    let isCancelled = false

    setAvailableAudioTracks([])
    onAudioTracksLoaded?.([])
    setAvailableQualities([])
    onQualityLevelsLoaded?.([])
    setHlsSubtitles([])
    onSubtitleTracksLoaded?.([])

    // Validate video URL first
    const validation = validateVideoURL(src)
    if (!validation.valid) {
      const error = new Error(validation.error || 'Invalid video URL')
      setVideoState((prev) => ({ ...prev, error, loading: false }))
      onError?.(error)
      return
    }

    // Detect video protocol
    const detectedProtocol = detectVideoProtocol(src)
    const detection =
      protocol === 'auto'
        ? detectedProtocol
        : {
            ...detectedProtocol,
            protocol,
            needsSpecialPlayer: protocol !== 'native',
          }
    let cleanupFn: (() => void) | null = null

    const teardownPlayer = () => {
      if (cleanupFn) {
        cleanupFn()
        cleanupFn = null
      }

      // Also check for any lingering player instances
      if ((video as any).__hlsInstance) {
        const hls = (video as any).__hlsInstance
        if (hls && typeof hls.destroy === 'function') {
          hls.destroy()
        }
        delete (video as any).__hlsInstance
      }

      if ((video as any).__rtmpInstance) {
        const rtmp = (video as any).__rtmpInstance
        if (rtmp && typeof rtmp.destroy === 'function') {
          rtmp.destroy()
        }
        delete (video as any).__rtmpInstance
      }

      if ((video as any).__mpegtsInstance) {
        const mpegts = (video as any).__mpegtsInstance
        if (mpegts && typeof mpegts.destroy === 'function') {
          mpegts.destroy()
        }
        delete (video as any).__mpegtsInstance
      }
    }

    logger.log('[VideoElement] Source:', src)
    logger.log('[VideoElement] Detected protocol:', detection.protocol)
    logger.log('[VideoElement] Is live stream?', detection.isLive)
    logger.log('[VideoElement] Needs special player?', detection.needsSpecialPlayer)

    const setupPlayer = async () => {
      try {
        switch (detection.protocol) {
          case 'hls': {
            // HLS streaming setup
            const canPlayHLS = video.canPlayType('application/vnd.apple.mpegurl')
            const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
            const shouldUseHlsJs = canPlayHLS === '' || !isSafari

            logger.log('[VideoElement] Native HLS support?', canPlayHLS)
            logger.log('[VideoElement] Is Safari?', isSafari)
            logger.log('[VideoElement] Will use HLS.js?', shouldUseHlsJs)

            if (shouldUseHlsJs) {
              logger.log('[VideoElement] Setting up HLS.js...')
              cleanupFn = await setupHlsInstance({
                video,
                src,
                autoplay,
                onAudioTracksLoaded: (tracks) => {
                  if (isCancelled) return
                  setAvailableAudioTracks(tracks)
                  onAudioTracksLoaded?.(tracks)
                },
                onQualityLevelsLoaded: (qualities) => {
                  if (isCancelled) return
                  setAvailableQualities(qualities)
                  onQualityLevelsLoaded?.(qualities)
                },
                onSubtitleTracksLoaded: (tracks) => {
                  if (isCancelled) return
                  setHlsSubtitles(tracks)
                  onSubtitleTracksLoaded?.(tracks)
                },
                onError: handleError,
              })

              if (isCancelled) {
                teardownPlayer()
                return
              }
            } else {
              if (isCancelled) return
              logger.log('[VideoElement] Using native HLS playback')
              video.src = src
              if (autoplay) {
                void video.play().catch(() => undefined)
              }
            }
            break
          }

          case 'rtmp': {
            // RTMP/FLV streaming setup
            logger.log('[VideoElement] Setting up RTMP/FLV player...')
            cleanupFn = await setupRtmpInstance({
              video,
              src,
              autoplay,
              onError: handleError,
              onLoadedMetadata,
            })

            if (isCancelled) {
              teardownPlayer()
              return
            }
            break
          }

          case 'mpegts': {
            // MPEG-TS/IPTV streaming setup
            logger.log('[VideoElement] Setting up MPEG-TS player...')
            cleanupFn = await setupMpegtsInstance({
              video,
              src,
              autoplay,
              onError: handleError,
              onLoadedMetadata,
            })

            if (isCancelled) {
              teardownPlayer()
              return
            }
            break
          }

          case 'dash': {
            // DASH streaming - not yet implemented
            if (isCancelled) return
            const error = new Error('DASH streaming is not yet supported')
            logger.error('[VideoElement]', error.message)
            setVideoState((prev) => ({ ...prev, error, loading: false }))
            onError?.(error)
            break
          }

          case 'native':
          default: {
            // Native HTML5 video (MP4, WebM, etc.)
            if (isCancelled) return
            logger.log('[VideoElement] Using native video.src')
            video.src = src
            if (autoplay) {
              void video.play().catch(() => undefined)
            }
            break
          }
        }
      } catch (err) {
        let error: Error
        if (err instanceof Error && isCORSError(err)) {
          const corsMessage = getCORSErrorMessage(src)
          error = new Error(corsMessage)
        } else {
          error = err instanceof Error ? err : new Error(`Failed to load ${detection.protocol.toUpperCase()} video`)
        }

        if (isCancelled) return

        logger.error('[VideoElement] Setup error:', error)
        setVideoState((prev) => ({
          ...prev,
          error,
          loading: false,
        }))
        onError?.(error)
      }
    }

    void setupPlayer()

    // Cleanup function
    return () => {
      isCancelled = true
      teardownPlayer()
    }
  }, [
    src,
    protocol,
    autoplay,
    videoRef,
    handleError,
    setVideoState,
    onError,
    onAudioTracksLoaded,
    onQualityLevelsLoaded,
    onSubtitleTracksLoaded,
    onLoadedMetadata,
  ])

  // Handle audio track changes
  useEffect(() => {
    const video = videoRef.current
    if (!video || !settings.audioTrack) return

    const hlsInstance = (video as any).__hlsInstance
    if (!hlsInstance) return

    // Find the index of the selected audio track
    const trackIndex = availableAudioTracks.findIndex(
      (track) => track.language === settings.audioTrack?.language
    )

    if (trackIndex !== -1) {
      setHlsAudioTrack(hlsInstance, trackIndex)
    }
  }, [settings.audioTrack, availableAudioTracks, videoRef])

  // Reset selected quality if it no longer exists
  useEffect(() => {
    if (!settings.quality) return
    if (availableQualities.length === 0) return

    const hasQuality = availableQualities.some((quality) => {
      if (
        typeof settings.quality?.levelIndex === 'number' &&
        typeof quality.levelIndex === 'number' &&
        quality.levelIndex === settings.quality.levelIndex
      ) {
        return true
      }

      if (
        typeof settings.quality?.height === 'number' &&
        typeof quality.height === 'number' &&
        quality.height === settings.quality.height
      ) {
        return true
      }

      return quality.label === settings.quality?.label
    })

    if (!hasQuality) {
      setQuality(null)
    }
  }, [availableQualities, settings.quality, setQuality])

  // Apply selected quality to HLS instance
  useEffect(() => {
    const video = videoRef.current
    if (!video) return

    const hlsInstance = (video as any).__hlsInstance
    if (!hlsInstance) return

    if (!settings.quality) {
      setHlsQualityLevel(hlsInstance, null)
      return
    }

    let targetLevelIndex =
      typeof settings.quality.levelIndex === 'number' ? settings.quality.levelIndex : undefined

    if (typeof targetLevelIndex !== 'number') {
      const matchingQuality = availableQualities.find((quality) => {
        if (
          typeof settings.quality?.levelIndex === 'number' &&
          typeof quality.levelIndex === 'number'
        ) {
          return quality.levelIndex === settings.quality.levelIndex
        }

        if (
          typeof settings.quality?.height === 'number' &&
          typeof quality.height === 'number'
        ) {
          return quality.height === settings.quality.height
        }

        return quality.label === settings.quality?.label
      })

      if (matchingQuality && typeof matchingQuality.levelIndex === 'number') {
        targetLevelIndex = matchingQuality.levelIndex
      }
    }

    setHlsQualityLevel(hlsInstance, targetLevelIndex)
  }, [settings.quality, availableQualities, videoRef])

  // Handle subtitle track changes
  useEffect(() => {
    const video = videoRef.current
    if (!video) return

    const tracks = video.textTracks
    if (!tracks || tracks.length === 0) return

    const enableSubtitle = () => {
      // Disable all tracks first
      for (let i = 0; i < tracks.length; i++) {
        tracks[i].mode = 'hidden'
      }

      // Enable the selected subtitle track
      if (settings.subtitle) {
        for (let i = 0; i < tracks.length; i++) {
          const track = tracks[i]
          if (track.language === settings.subtitle.lang) {
            // Wait for track to have cues before showing
            if (track.cues && track.cues.length > 0) {
              track.mode = 'showing'
              logger.log(`🔊 Enabled subtitle track: ${track.label} (${track.language})`)
              logger.log(`   - cues available: ${track.cues.length}`)
              logger.log(`   - track.mode: ${track.mode}`)
            } else {
              logger.warn(`⚠️ Track ${track.label} has no cues yet, waiting...`)
              // Track not ready yet, will be handled by load event
              track.mode = 'showing'
            }
            break
          }
        }
      }
    }

    // Try to enable immediately
    enableSubtitle()

    // Also listen for track load events to retry
    const handleTrackChange = () => {
      logger.log(`🔄 Track changed, re-enabling subtitle`)
      enableSubtitle()
    }

    for (let i = 0; i < tracks.length; i++) {
      tracks[i].addEventListener('load', handleTrackChange)
    }

    return () => {
      for (let i = 0; i < tracks.length; i++) {
        tracks[i].removeEventListener('load', handleTrackChange)
      }
    }
  }, [settings.subtitle, videoRef])

  return (
    <div className="video-container">
      <video
        ref={videoRef}
        className="video-element"
        poster={poster}
        loop={loop}
        muted={muted}
        crossOrigin={crossOrigin}
        playsInline={playsInline}
        preload={preload}
        controlsList={controlsList}
        onPlay={handlePlay}
        onPause={handlePause}
        onTimeUpdate={handleTimeUpdate}
        onLoadedMetadata={handleLoadedMetadata}
        onDurationChange={handleDurationChange}
        onVolumeChange={handleVolumeChange}
        onSeeking={handleSeeking}
        onSeeked={handleSeeked}
        onWaiting={handleWaiting}
        onCanPlay={handleCanPlay}
        onProgress={handleProgress}
        onRateChange={handleRateChange}
        onEnded={handleEnded}
        onError={handleError}
        onClick={handleVideoClick}
      >
        {processedSubtitles.map((subtitle, index) => (
          <track
            key={index}
            kind="subtitles"
            src={subtitle.src}
            srcLang={subtitle.lang}
            label={subtitle.label}
            default={subtitle.default}
          />
        ))}
      </video>
    </div>
  )
}

