import { ReactNode, useCallback, useEffect, useState } from 'react';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { decryptFile, downloadEncryptedMedia, downloadMedia, mxcUrlToHttp } from '../../../utils/matrix';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { getCurrentAccessToken } from '../../../utils/auth';

/**
 * Extracts the first frame from a video file and renders it as a thumbnail.
 * This is used as a fallback when no thumbnail metadata is provided. 
 */
export type VideoFrameThumbnailProps = {
  mimeType: string;
  url: string;
  encInfo?: EncryptedAttachmentInfo;
  renderImage: (src: string) => ReactNode;
};

export function VideoFrameThumbnail({
  mimeType,
  url,
  encInfo,
  renderImage,
}: VideoFrameThumbnailProps) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const [thumbnailSrc, setThumbnailSrc] = useState<string | null>(null);

  const [videoSrcState, loadVideoSrc] = useAsyncCallback(
    useCallback(async () => {
      const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
      const accessToken = getCurrentAccessToken();
      
      const fileContent = encInfo
        ? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
            decryptFile(encBuf, mimeType, encInfo), accessToken
          )
        : await downloadMedia(mediaUrl, accessToken);
      
      return URL.createObjectURL(fileContent);
    }, [mx, url, useAuthentication, mimeType, encInfo])
  );

  useEffect(() => {
    loadVideoSrc();
  }, [loadVideoSrc]);

  useEffect(() => {
    if (videoSrcState.status !== AsyncStatus.Success) return undefined;

    const videoUrl = videoSrcState.status === AsyncStatus.Success ? videoSrcState.data : null;
    if (!videoUrl) return undefined;

    const video = document.createElement('video');
    video.preload = 'metadata';
    video.muted = true;
    video.playsInline = true;
    video.crossOrigin = 'anonymous';
    video.src = videoUrl;

    let isCleanedUp = false;
    const handlers: {
      loadedData?: () => void;
      seeked?: () => void;
      error?: () => void;
    } = {};

    const cleanup = () => {
      if (isCleanedUp) return;
      isCleanedUp = true;
      
      if (handlers.loadedData) video.removeEventListener('loadeddata', handlers.loadedData);
      if (handlers.seeked) video.removeEventListener('seeked', handlers.seeked);
      if (handlers.error) video.removeEventListener('error', handlers.error);
      video.src = '';
      video.load();
    };

    handlers.loadedData = () => {
      try {
        video.currentTime = Math.min(0.1, video.duration / 2);
      } catch {
        cleanup();
      }
    };

    handlers.seeked = () => {
      try {
        const canvas = document.createElement('canvas');
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        
        const ctx = canvas.getContext('2d');
        if (!ctx) {
          cleanup();
          return;
        }

        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        
        canvas.toBlob((blob) => {
          if (blob) {
            const thumbnailUrl = URL.createObjectURL(blob);
            setThumbnailSrc(thumbnailUrl);
          }
          cleanup();
        }, 'image/jpeg', 0.85);
      } catch {
        cleanup();
      }
    };

    handlers.error = () => {
      cleanup();
    };

    video.addEventListener('loadeddata', handlers.loadedData);
    video.addEventListener('seeked', handlers.seeked);
    video.addEventListener('error', handlers.error);

    return () => {
      cleanup();
      if (thumbnailSrc) {
        URL.revokeObjectURL(thumbnailSrc);
      }
      URL.revokeObjectURL(videoUrl);
    };
  }, [videoSrcState, thumbnailSrc]);

  return thumbnailSrc ? renderImage(thumbnailSrc) : null;
}
