/* @vitest-environment jsdom */

import { act, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';

import { PlatformProfileQrScannerModal } from './PlatformProfileQrScannerModal';

type MockTrack = {
  stop: ReturnType<typeof vi.fn>;
};

type MockStream = {
  getTracks: () => MockTrack[];
};

const originalBarcodeDetector = (
  globalThis as typeof globalThis & {
    BarcodeDetector?: unknown;
  }
).BarcodeDetector;

describe('PlatformProfileQrScannerModal', () => {
  beforeEach(() => {
    vi.useFakeTimers();
    vi.spyOn(HTMLMediaElement.prototype, 'play').mockResolvedValue(undefined);
    Object.defineProperty(HTMLMediaElement.prototype, 'readyState', {
      configurable: true,
      get: () => 4,
    });
  });

  afterEach(() => {
    vi.runOnlyPendingTimers();
    vi.useRealTimers();
    vi.restoreAllMocks();
    if (originalBarcodeDetector === undefined) {
      delete (
        globalThis as typeof globalThis & {
          BarcodeDetector?: unknown;
        }
      ).BarcodeDetector;
    } else {
      (
        globalThis as typeof globalThis & {
          BarcodeDetector?: unknown;
        }
      ).BarcodeDetector = originalBarcodeDetector;
    }
  });

  test('detects qr result and stops camera tracks', async () => {
    const stop = vi.fn();
    const stream = buildStream([{ stop }]);
    const getUserMedia = vi.fn().mockResolvedValue(stream);
    const detect = vi.fn().mockResolvedValue([{ rawValue: '  hello-world  ' }]);
    const onResult = vi.fn();

    installMediaDevices(getUserMedia);
    installBarcodeDetector(detect);

    render(
      <PlatformProfileQrScannerModal
        error={null}
        result={null}
        onClose={vi.fn()}
        onError={vi.fn()}
        onResult={onResult}
      />,
    );

    await act(async () => {
      await flushPromises();
    });
    expect(getUserMedia).toHaveBeenCalledTimes(1);

    await act(async () => {
      vi.advanceTimersByTime(360);
      await flushPromises();
    });

    expect(onResult).toHaveBeenCalledWith('hello-world');
    expect(detect).toHaveBeenCalledTimes(1);
    expect(stop).toHaveBeenCalledTimes(1);
  });

  test('releases camera resource when modal unmounts before recognition', async () => {
    const stop = vi.fn();
    const stream = buildStream([{ stop }]);
    const getUserMedia = vi.fn().mockResolvedValue(stream);
    const detect = vi.fn().mockResolvedValue([]);

    installMediaDevices(getUserMedia);
    installBarcodeDetector(detect);

    const { unmount } = render(
      <PlatformProfileQrScannerModal
        error={null}
        result={null}
        onClose={vi.fn()}
        onError={vi.fn()}
        onResult={vi.fn()}
      />,
    );

    await act(async () => {
      await flushPromises();
    });
    expect(getUserMedia).toHaveBeenCalledTimes(1);

    unmount();

    expect(stop).toHaveBeenCalledTimes(1);
    expect(screen.queryByRole('dialog', { name: '扫码' })).toBeNull();
  });
});

function buildStream(tracks: MockTrack[]): MockStream {
  return {
    getTracks: () => tracks,
  };
}

function installMediaDevices(getUserMedia: ReturnType<typeof vi.fn>) {
  Object.defineProperty(globalThis.navigator, 'mediaDevices', {
    configurable: true,
    value: { getUserMedia },
  });
}

function installBarcodeDetector(detect: ReturnType<typeof vi.fn>) {
  class MockBarcodeDetector {
    detect = detect;
  }

  (
    globalThis as typeof globalThis & {
      BarcodeDetector?: unknown;
    }
  ).BarcodeDetector = MockBarcodeDetector;
}

async function flushPromises() {
  await Promise.resolve();
}
