import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type RefObject,
} from 'react';
import { logger } from '../lib/logger';

interface UseReversePlaybackOptions {
  videoRef: RefObject<HTMLVideoElement | null>;
  onComplete: () => void;
  preloadedUrls?: Set<string>;
  videoUrl?: string;
  getCachedBlobUrl?: (url: string) => Promise<string | null>;
}

interface UseReversePlaybackResult {
  startReverse: () => Promise<void>;
  stopReverse: () => void;
  isReversing: boolean;
  isBuffering: boolean;
  canUseNativeReverse: boolean;
}

function checkNativeReverseSupport(): boolean {
  if (typeof window === 'undefined') return false;
  try {
    const video = document.createElement('video');
    video.playbackRate = -1;
    return video.playbackRate === -1;
  } catch {
    return false;
  }
}

async function startNativeReverse(
  video: HTMLVideoElement,
  duration: number,
  onComplete: () => void,
  didFinishRef: { current: boolean },
): Promise<boolean> {
  return new Promise((resolve) => {
    video.currentTime = duration;
    video.playbackRate = -1;

    const cleanup = () => {
      video.removeEventListener('timeupdate', onTimeUpdate);
      video.playbackRate = 1;
    };

    const onTimeUpdate = () => {
      if (didFinishRef.current) {
        cleanup();
        resolve(true);
        return;
      }
      if (video.currentTime <= 0.05) {
        cleanup();
        video.pause();
        video.currentTime = 0;
        onComplete();
        resolve(true);
      }
    };

    video.addEventListener('timeupdate', onTimeUpdate);

    video.play().catch((err) => {
      logger.error('Native reverse play failed:', err);
      cleanup();
      resolve(false);
    });
  });
}

function getBufferedEnd(video: HTMLVideoElement): number {
  return video.buffered.length > 0
    ? video.buffered.end(video.buffered.length - 1)
    : 0;
}

export function useReversePlayback(
  options: UseReversePlaybackOptions,
): UseReversePlaybackResult {
  const [isReversing, setIsReversing] = useState(false);
  const [isBuffering, setIsBuffering] = useState(false);
  const animationFrameRef = useRef<number | null>(null);
  const didFinishRef = useRef(false);
  const cleanupFnsRef = useRef<Array<() => void>>([]);

  const canUseNativeReverse = useMemo(() => checkNativeReverseSupport(), []);

  const cleanup = useCallback(() => {
    didFinishRef.current = true;
    if (animationFrameRef.current !== null) {
      cancelAnimationFrame(animationFrameRef.current);
      animationFrameRef.current = null;
    }
    cleanupFnsRef.current.forEach((fn) => fn());
    cleanupFnsRef.current = [];
    const video = options.videoRef.current;
    if (video && video.playbackRate !== 1) {
      video.playbackRate = 1;
    }
  }, [options.videoRef]);

  const stopReverse = useCallback(() => {
    cleanup();
    setIsReversing(false);
    setIsBuffering(false);
  }, [cleanup]);

  const startReverse = useCallback(async () => {
    const video = options.videoRef.current;
    if (!video) return;

    stopReverse();
    didFinishRef.current = false;
    setIsReversing(true);

    const { onComplete, preloadedUrls, videoUrl, getCachedBlobUrl } = options;

    const actualDuration = video.duration;
    if (!Number.isFinite(actualDuration) || actualDuration <= 0) {
      logger.error('Invalid video duration for reverse playback', {
        videoDuration: video.duration,
        readyState: video.readyState,
        videoUrl,
      });
      setIsReversing(false);
      onComplete();
      return;
    }

    const finishReverse = (reason: string) => {
      if (didFinishRef.current) return;
      didFinishRef.current = true;
      cleanup();
      setIsReversing(false);
      setIsBuffering(false);
      logger.info('Reverse playback finished', { reason });
      onComplete();
    };

    // Strategy 1: Native playbackRate = -1
    if (canUseNativeReverse) {
      logger.info('Using native playbackRate = -1');
      const success = await startNativeReverse(
        video,
        actualDuration,
        () => finishReverse('native-complete'),
        didFinishRef,
      );
      if (success) return;
      logger.info('Native reverse failed, falling back to frame-stepping');
    }

    // Check if video is preloaded
    const isPreloaded = videoUrl && preloadedUrls?.has(videoUrl);
    const fps = isPreloaded ? 30 : 15;
    const stepSize = 1 / fps;

    // Try to get blob URL from cache for better seeking
    if (getCachedBlobUrl && videoUrl && !video.src.startsWith('blob:')) {
      try {
        const blobUrl = await getCachedBlobUrl(videoUrl);
        if (blobUrl && !didFinishRef.current) {
          logger.info('Using cached blob URL for reverse');
          video.src = blobUrl;
          video.load();
          await new Promise<void>((resolve) => {
            const onLoaded = () => {
              video.removeEventListener('loadeddata', onLoaded);
              resolve();
            };
            video.addEventListener('loadeddata', onLoaded);
            setTimeout(resolve, 2000);
          });
        }
      } catch (err) {
        logger.warn('Failed to get cached blob URL', { err });
      }
    }

    const startFrameStepping = (targetTime: number) => {
      const maxBuffered = getBufferedEnd(video);
      const safeTarget = Math.min(targetTime, maxBuffered);

      logger.info('Preparing reverse seek', {
        requestedTarget: targetTime,
        maxBuffered,
        safeTarget,
        readyState: video.readyState,
      });

      if (safeTarget < 0.1) {
        logger.info('No buffered range available');
        finishReverse('no-buffered');
        return;
      }

      const beginFrameStepping = () => {
        video.pause();
        let lastFrameTime = performance.now();
        let stepCount = 0;

        const step = (currentFrameTime: number) => {
          if (didFinishRef.current) {
            animationFrameRef.current = null;
            return;
          }

          if (!video.paused) {
            video.pause();
          }

          const elapsed = currentFrameTime - lastFrameTime;
          if (elapsed < 1000 / fps) {
            animationFrameRef.current = requestAnimationFrame(step);
            return;
          }
          lastFrameTime = currentFrameTime;
          stepCount++;

          const currentTime = video.currentTime;
          const newTime = currentTime - stepSize;

          if (stepCount % 30 === 0) {
            logger.info('Frame-stepping progress', { stepCount, currentTime });
          }

          if (newTime <= 0) {
            animationFrameRef.current = null;
            video.currentTime = 0;
            finishReverse('reverse-complete');
          } else {
            video.currentTime = newTime;
            animationFrameRef.current = requestAnimationFrame(step);
          }
        };

        animationFrameRef.current = requestAnimationFrame(step);
      };

      const onSeeked = () => {
        video.removeEventListener('seeked', onSeeked);
        if (didFinishRef.current) return;

        if (video.currentTime < 0.1) {
          logger.info('Seek failed, retrying');
          const onRetry = () => {
            video.removeEventListener('seeked', onRetry);
            if (didFinishRef.current) return;
            if (video.currentTime >= 0.1) {
              beginFrameStepping();
            } else {
              finishReverse('seek-failed');
            }
          };
          video.addEventListener('seeked', onRetry);
          cleanupFnsRef.current.push(() =>
            video.removeEventListener('seeked', onRetry),
          );
          video.currentTime = safeTarget;
          return;
        }

        beginFrameStepping();
      };

      video.addEventListener('seeked', onSeeked);
      cleanupFnsRef.current.push(() =>
        video.removeEventListener('seeked', onSeeked),
      );

      if (!video.paused) {
        const onPause = () => {
          video.removeEventListener('pause', onPause);
          if (didFinishRef.current) return;
          requestAnimationFrame(() => {
            if (!didFinishRef.current) {
              video.currentTime = safeTarget;
            }
          });
        };
        video.addEventListener('pause', onPause);
        cleanupFnsRef.current.push(() =>
          video.removeEventListener('pause', onPause),
        );
        video.pause();
      } else {
        video.currentTime = safeTarget;
      }
    };

    const bufferedEnd = getBufferedEnd(video);

    logger.info('Starting reverse playback', {
      duration: actualDuration,
      bufferedEnd,
      isPreloaded,
      readyState: video.readyState,
    });

    // If already fully buffered, start immediately
    if (bufferedEnd >= actualDuration - 0.1) {
      logger.info('Video fully buffered, starting immediately');
      startFrameStepping(actualDuration);
      return;
    }

    // Need to buffer more
    setIsBuffering(true);
    let progressCheckCount = 0;
    const maxProgressChecks = 160; // ~8 seconds at 20 checks/sec

    const onProgress = () => {
      if (didFinishRef.current) return;
      progressCheckCount++;
      const currentBuffered = getBufferedEnd(video);

      if (currentBuffered >= actualDuration - 0.1) {
        setIsBuffering(false);
        startFrameStepping(actualDuration);
        return;
      }

      if (progressCheckCount >= maxProgressChecks) {
        setIsBuffering(false);
        if (currentBuffered >= 0.5) {
          startFrameStepping(currentBuffered);
        } else {
          finishReverse('buffer-timeout');
        }
      }
    };

    const onCanPlayThrough = () => {
      if (didFinishRef.current) return;
      const currentBuffered = getBufferedEnd(video);
      if (currentBuffered >= 0.5) {
        setIsBuffering(false);
        const targetTime = Math.min(currentBuffered, actualDuration);
        startFrameStepping(targetTime);
      }
    };

    const onEnded = () => {
      if (didFinishRef.current) return;
      setIsBuffering(false);
      const currentBuffered = getBufferedEnd(video);
      if (currentBuffered >= 0.5) {
        startFrameStepping(currentBuffered);
      } else {
        startFrameStepping(actualDuration);
      }
    };

    video.addEventListener('progress', onProgress);
    video.addEventListener('canplaythrough', onCanPlayThrough);
    video.addEventListener('ended', onEnded);
    cleanupFnsRef.current.push(
      () => video.removeEventListener('progress', onProgress),
      () => video.removeEventListener('canplaythrough', onCanPlayThrough),
      () => video.removeEventListener('ended', onEnded),
    );

    // Start playback to trigger buffering
    video.muted = true;
    video.currentTime = 0;
    video.play().catch(() => undefined);

    // Fallback timeout
    const timeoutId = setTimeout(() => {
      if (didFinishRef.current) return;
      setIsBuffering(false);
      const currentBuffered = getBufferedEnd(video);
      if (currentBuffered >= 0.5) {
        startFrameStepping(currentBuffered);
      } else {
        finishReverse('buffer-timeout');
      }
    }, 8000);
    cleanupFnsRef.current.push(() => clearTimeout(timeoutId));
  }, [canUseNativeReverse, options, stopReverse, cleanup]);

  useEffect(() => () => stopReverse(), [stopReverse]);

  return {
    startReverse,
    stopReverse,
    isReversing,
    isBuffering,
    canUseNativeReverse,
  };
}
