import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import type React from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AssetItem } from './AssetItem';
import { AssetSource, AssetType, type Asset } from '../../types/asset.types';

vi.mock('tdesign-react', () => ({
  Checkbox: ({ checked, onChange }: any) => (
    <input
      aria-label="选择资产"
      type="checkbox"
      checked={checked}
      onChange={(event) => onChange?.(event.target.checked)}
    />
  ),
}));

vi.mock('../lazy-image', () => ({
  LazyImage: ({ src, alt, className, objectFit }: any) => (
    <img
      src={src}
      alt={alt}
      className={className}
      data-object-fit={objectFit}
    />
  ),
}));

vi.mock('../shared/VideoPosterPreview', () => ({
  VideoPosterPreview: ({ src, poster, alt, className, fit }: any) => (
    <img
      src={poster || src}
      alt={alt}
      className={`video-poster-preview__media ${className || ''}`}
      data-fit={fit}
    />
  ),
}));

vi.mock('../shared/hover', () => ({
  HoverTip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));

vi.mock('../../hooks/useAssetSize', () => ({
  useAssetSize: (_id: string, _url: string, size?: number) => size,
}));

vi.mock('../../hooks/useThumbnailUrl', () => ({
  useThumbnailUrl: (url: string) => url,
}));

vi.mock('../../hooks/useUnifiedCache', () => ({
  useUnifiedCache: () => ({
    isCached: true,
    cacheWarning: undefined,
  }),
}));

const baseAsset = {
  source: AssetSource.LOCAL,
  createdAt: 1,
  size: 4096,
};

const videoAsset: Asset = {
  ...baseAsset,
  id: 'video-asset',
  type: AssetType.VIDEO,
  url: '/media/demo.mp4',
  thumbnail: '/media/demo-poster.jpg',
  name: '视频素材',
  mimeType: 'video/mp4',
};

const imageAsset: Asset = {
  ...baseAsset,
  id: 'image-asset',
  type: AssetType.IMAGE,
  url: '/media/demo.jpg',
  name: '图片素材',
  mimeType: 'image/jpeg',
};

const audioAsset: Asset = {
  ...baseAsset,
  id: 'audio-asset',
  type: AssetType.AUDIO,
  url: '/media/demo.mp3',
  thumbnail: '/media/demo-cover.jpg',
  name: '音频素材',
  mimeType: 'audio/mpeg',
};

function renderAssetItem(
  asset: Asset,
  props: Partial<React.ComponentProps<typeof AssetItem>> = {}
) {
  return render(
    <AssetItem
      asset={asset}
      viewMode="grid"
      isSelected={false}
      onSelect={vi.fn()}
      {...props}
    />
  );
}

describe('AssetItem hover preview', () => {
  beforeEach(() => {
    vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockResolvedValue();
    vi.spyOn(window.HTMLMediaElement.prototype, 'pause').mockImplementation(
      () => undefined
    );
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('plays video on hover and restores the poster on mouse leave', async () => {
    const { container } = renderAssetItem(videoAsset);
    const item = container.querySelector('.asset-item') as HTMLElement;

    expect(container.querySelector('.asset-item__hover-video')).toBeNull();

    fireEvent.mouseEnter(item);

    const hoverVideo = await waitFor(() => {
      const node = container.querySelector('.asset-item__hover-video');
      expect(node).toBeInstanceOf(HTMLVideoElement);
      return node as HTMLVideoElement;
    });

    expect(hoverVideo.getAttribute('src')).toBe(videoAsset.url);
    expect(hoverVideo.muted).toBe(true);
    expect(hoverVideo.loop).toBe(true);
    await waitFor(() => {
      expect(window.HTMLMediaElement.prototype.play).toHaveBeenCalled();
    });

    fireEvent.mouseLeave(item);

    await waitFor(() => {
      expect(container.querySelector('.asset-item__hover-video')).toBeNull();
    });
    expect(screen.getByAltText(videoAsset.name)).not.toBeNull();
  });

  it('plays audio on hover and restores the cover on mouse leave', async () => {
    const { container } = renderAssetItem(audioAsset);
    const item = container.querySelector('.asset-item') as HTMLElement;

    expect(container.querySelector('.asset-item__hover-audio')).toBeNull();
    expect(screen.getByAltText(audioAsset.name)).not.toBeNull();

    fireEvent.mouseEnter(item);

    const hoverAudio = await waitFor(() => {
      const node = container.querySelector('.asset-item__hover-audio');
      expect(node).toBeInstanceOf(HTMLAudioElement);
      return node as HTMLAudioElement;
    });

    expect(hoverAudio.getAttribute('src')).toBe(audioAsset.url);
    await waitFor(() => {
      expect(window.HTMLMediaElement.prototype.play).toHaveBeenCalled();
    });

    fireEvent.mouseLeave(item);

    await waitFor(() => {
      expect(container.querySelector('.asset-item__hover-audio')).toBeNull();
    });
    expect(screen.getByAltText(audioAsset.name)).not.toBeNull();
  });

  it('does not start video or audio hover playback when disabled', async () => {
    const { container, rerender } = renderAssetItem(videoAsset, {
      disableHoverPlayback: true,
    });
    const videoItem = container.querySelector('.asset-item') as HTMLElement;

    fireEvent.mouseEnter(videoItem);

    expect(container.querySelector('.asset-item__hover-video')).toBeNull();
    expect(window.HTMLMediaElement.prototype.play).not.toHaveBeenCalled();

    rerender(
      <AssetItem
        asset={audioAsset}
        viewMode="grid"
        isSelected={false}
        onSelect={vi.fn()}
        disableHoverPlayback
      />
    );
    const audioItem = container.querySelector('.asset-item') as HTMLElement;

    fireEvent.mouseEnter(audioItem);

    expect(container.querySelector('.asset-item__hover-audio')).toBeNull();
    expect(window.HTMLMediaElement.prototype.play).not.toHaveBeenCalled();
  });

  it('passes cover fit to image and video thumbnails when requested', () => {
    renderAssetItem(imageAsset, { thumbnailFit: 'cover' });
    expect(
      screen.getByAltText(imageAsset.name).getAttribute('data-object-fit')
    ).toBe('cover');

    renderAssetItem(videoAsset, { thumbnailFit: 'cover' });
    expect(screen.getByAltText(videoAsset.name).getAttribute('data-fit')).toBe(
      'cover'
    );
  });
});
