import { render } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { HlsPlayer } from './HlsPlayer';

const hlsMock = {
  isSupported: true,
  loadSource: vi.fn(),
  attachMedia: vi.fn(),
  on: vi.fn(),
  destroy: vi.fn(),
  startLoad: vi.fn(),
  recoverMediaError: vi.fn()
};

vi.mock('hls.js', () => {
  class MockHls {
    static isSupported() {
      return hlsMock.isSupported;
    }
    static Events = { MANIFEST_PARSED: 'hlsManifestParsed', ERROR: 'hlsError' };
    loadSource = hlsMock.loadSource;
    attachMedia = hlsMock.attachMedia;
    on = hlsMock.on;
    destroy = hlsMock.destroy;
    startLoad = hlsMock.startLoad;
    recoverMediaError = hlsMock.recoverMediaError;
  }
  return { default: MockHls };
});

describe('HlsPlayer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    hlsMock.isSupported = true;
  });

  it('reports a fatal hls.js error through onError', () => {
    const onError = vi.fn();
    render(<HlsPlayer onError={onError} src="/iptv/channel/12.1.m3u8" />);

    const errorHandler = hlsMock.on.mock.calls.find((call) => call[0] === 'hlsError')?.[1];
    expect(errorHandler).toBeTypeOf('function');

    errorHandler({}, { fatal: true, type: 'networkError', details: 'manifestLoadError' });

    expect(onError).toHaveBeenCalledWith('networkError: manifestLoadError');
  });

  it('ignores non-fatal hls.js errors', () => {
    const onError = vi.fn();
    render(<HlsPlayer onError={onError} src="/iptv/channel/12.1.m3u8" />);

    const errorHandler = hlsMock.on.mock.calls.find((call) => call[0] === 'hlsError')?.[1];
    errorHandler({}, { fatal: false, type: 'mediaError', details: 'bufferStalledError' });

    expect(onError).not.toHaveBeenCalled();
  });

  it('does not auto-recover a fatal error', () => {
    render(<HlsPlayer onError={vi.fn()} src="/iptv/channel/12.1.m3u8" />);

    const errorHandler = hlsMock.on.mock.calls.find((call) => call[0] === 'hlsError')?.[1];
    errorHandler({}, { fatal: true, type: 'networkError', details: 'manifestLoadError' });

    // A diagnostic player must surface the fault, not silently retry past it — auto-recovery is
    // hls.js's own startLoad()/recoverMediaError(), so assert those directly rather than only
    // inferring it from loadSource's call count.
    expect(hlsMock.loadSource).toHaveBeenCalledTimes(1);
    expect(hlsMock.startLoad).not.toHaveBeenCalled();
    expect(hlsMock.recoverMediaError).not.toHaveBeenCalled();
  });

  it('does not report onPlaying when hls.js merely parses the manifest', () => {
    // MANIFEST_PARSED fires before any media has decoded — an HttpLiveStreamingDirect manifest
    // always parses even over a black video, so onPlaying must not be driven by it.
    const onPlaying = vi.fn();
    render(<HlsPlayer onPlaying={onPlaying} src="/iptv/channel/12.1.m3u8" />);

    const manifestHandler = hlsMock.on.mock.calls.find((call) => call[0] === 'hlsManifestParsed')?.[1];
    expect(manifestHandler).toBeTypeOf('function');

    manifestHandler();

    expect(onPlaying).not.toHaveBeenCalled();
  });

  it('reports onPlaying when the video element fires its real playing event', () => {
    const onPlaying = vi.fn();
    const { container } = render(<HlsPlayer onPlaying={onPlaying} src="/iptv/channel/12.1.m3u8" />);

    const video = container.querySelector('video');
    expect(video).not.toBeNull();

    video?.dispatchEvent(new Event('playing'));

    expect(onPlaying).toHaveBeenCalledTimes(1);
  });

  it('is safe with no onPlaying provided', () => {
    const { container } = render(<HlsPlayer src="/iptv/channel/12.1.m3u8" />);

    const manifestHandler = hlsMock.on.mock.calls.find((call) => call[0] === 'hlsManifestParsed')?.[1];
    expect(() => manifestHandler()).not.toThrow();

    const video = container.querySelector('video');
    expect(() => video?.dispatchEvent(new Event('playing'))).not.toThrow();
  });
});
