import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import { AssetPicker, acceptFor, matchesMediaType, isStandalone } from './AssetPicker';
import type { Asset } from '../utils/assets';

vi.mock('../utils/assets', () => ({
  uploadAsset: vi.fn(),
  listAssets: vi.fn(),
}));

import { uploadAsset, listAssets } from '../utils/assets';

const uploadAssetMock = uploadAsset as unknown as ReturnType<typeof vi.fn>;
const listAssetsMock = listAssets as unknown as ReturnType<typeof vi.fn>;

const GRID_ASSETS: Asset[] = [
  { name: 'a.png', url: '/storage/assets/a.png', type: 'image/png' },
  { name: 'b.mp4', url: '/storage/assets/b.mp4', type: 'video/mp4' },
];

/* ---------- DOM test harness (no @testing-library/react in this repo, see
   src/utils/assets.test.ts / navColorFields.test.ts for the plain-logic
   pattern used elsewhere; component-level interaction here uses
   react-dom/client + react-dom/test-utils `act`, both already transitive
   deps of `react-dom`, so no new devDependency is introduced). ---------- */
let container: HTMLDivElement;
let root: Root;

function render(ui: React.ReactElement) {
  container = document.createElement('div');
  document.body.appendChild(container);
  act(() => {
    root = createRoot(container);
    root.render(ui);
  });
}

function unmount() {
  act(() => { root.unmount(); });
  container.remove();
}

function click(el: Element | null) {
  if (!el) throw new Error('element not found');
  act(() => { (el as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); });
}

function setValue(input: HTMLInputElement, value: string) {
  const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')!.set!;
  setter.call(input, value);
  input.dispatchEvent(new Event('input', { bubbles: true }));
}

function q<T extends Element = Element>(testId: string): T | null {
  return container.querySelector(`[data-testid="${testId}"]`);
}

describe('AssetPicker pure helpers', () => {
  test('acceptFor maps mediaType to the file input accept attribute', () => {
    expect(acceptFor('image')).toBe('image/*');
    expect(acceptFor('video')).toBe('video/*');
    expect(acceptFor('any')).toBeUndefined();
  });

  test('matchesMediaType guards drag-drop uploads by mediaType', () => {
    expect(matchesMediaType('image/png', 'image')).toBe(true);
    expect(matchesMediaType('video/mp4', 'image')).toBe(false);
    expect(matchesMediaType('video/mp4', 'video')).toBe(true);
    expect(matchesMediaType('application/pdf', 'any')).toBe(true);
  });

  test('isStandalone reflects presence of window.WHP_CONFIG', () => {
    delete (window as any).WHP_CONFIG;
    expect(isStandalone()).toBe(true);
    (window as any).WHP_CONFIG = { apiUrl: 'x', siteId: 1, csrfToken: 't' };
    expect(isStandalone()).toBe(false);
    delete (window as any).WHP_CONFIG;
  });
});

describe('AssetPicker component', () => {
  beforeEach(() => {
    uploadAssetMock.mockReset();
    listAssetsMock.mockReset();
    listAssetsMock.mockResolvedValue(GRID_ASSETS);
    delete (window as any).WHP_CONFIG;
  });

  afterEach(() => {
    if (container) unmount();
    delete (window as any).WHP_CONFIG;
  });

  test('renders the current value as a preview thumbnail (full variant)', () => {
    render(<AssetPicker value="/storage/assets/existing.png" onChange={vi.fn()} />);
    const preview = q<HTMLImageElement>('asset-picker-preview');
    expect(preview).not.toBeNull();
    expect(preview!.src).toContain('/storage/assets/existing.png');
    expect(q('asset-picker-dropzone')).toBeNull();
  });

  test('renders a dropzone (no preview) when value is empty', () => {
    render(<AssetPicker value="" onChange={vi.fn()} />);
    expect(q('asset-picker-dropzone')).not.toBeNull();
    expect(q('asset-picker-preview')).toBeNull();
  });

  test('remove button clears the value via onChange("")', () => {
    const onChange = vi.fn();
    render(<AssetPicker value="/storage/assets/existing.png" onChange={onChange} />);
    click(q('asset-picker-remove'));
    expect(onChange).toHaveBeenCalledWith('');
  });

  test('Browse control is absent when window.WHP_CONFIG is undefined (standalone)', () => {
    render(<AssetPicker value="" onChange={vi.fn()} />);
    expect(q('asset-picker-browse')).toBeNull();
  });

  test('Browse control is present and lists assets filtered by mediaType when WHP_CONFIG is set', async () => {
    (window as any).WHP_CONFIG = { apiUrl: 'https://x', siteId: 1, csrfToken: 't' };
    render(<AssetPicker value="" onChange={vi.fn()} mediaType="video" />);
    const browseBtn = q('asset-picker-browse');
    expect(browseBtn).not.toBeNull();

    await act(async () => { click(browseBtn); await Promise.resolve(); });

    expect(listAssetsMock).toHaveBeenCalledWith('video');
    expect(q('asset-picker-grid')).not.toBeNull();
  });

  test('selecting a grid asset invokes onChange with that asset url and closes the grid', async () => {
    (window as any).WHP_CONFIG = { apiUrl: 'https://x', siteId: 1, csrfToken: 't' };
    const onChange = vi.fn();
    render(<AssetPicker value="" onChange={onChange} mediaType="any" />);

    await act(async () => { click(q('asset-picker-browse')); await Promise.resolve(); });
    expect(q('asset-picker-grid')).not.toBeNull();

    click(q(`asset-picker-thumb-${GRID_ASSETS[0].name}`));

    expect(onChange).toHaveBeenCalledWith(GRID_ASSETS[0].url);
    expect(q('asset-picker-grid')).toBeNull();
  });

  test('URL apply invokes onChange with the typed url (full variant Apply button)', () => {
    const onChange = vi.fn();
    render(<AssetPicker value="" onChange={onChange} />);
    const input = q<HTMLInputElement>('asset-picker-url-input')!;
    setValue(input, 'https://example.com/pic.jpg');
    click(q('asset-picker-apply'));
    expect(onChange).toHaveBeenCalledWith('https://example.com/pic.jpg');
  });

  test('uploading a file calls uploadAsset and applies the resolved url via onChange', async () => {
    uploadAssetMock.mockResolvedValue('/storage/assets/uploaded.png');
    const onChange = vi.fn();
    render(<AssetPicker value="" onChange={onChange} />);

    const fileInput = q<HTMLInputElement>('asset-picker-file-input')!;
    const file = new File(['x'], 'photo.png', { type: 'image/png' });
    const dt = { files: [file] };
    Object.defineProperty(fileInput, 'files', { value: dt.files });

    await act(async () => {
      fileInput.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(uploadAssetMock).toHaveBeenCalledWith(file);
    expect(onChange).toHaveBeenCalledWith('/storage/assets/uploaded.png');
  });

  test('variant="compact" renders the compact single-row structure instead of the full structure', () => {
    render(<AssetPicker value="" onChange={vi.fn()} variant="compact" />);
    expect(q('asset-picker-compact')).not.toBeNull();
    expect(q('asset-picker-full')).toBeNull();
    expect(q('asset-picker-dropzone')).toBeNull();
    expect(q('asset-picker-url-input')).not.toBeNull();
    expect(q('asset-picker-upload')).not.toBeNull();
  });

  test('compact variant: URL input applies onChange on blur', () => {
    const onChange = vi.fn();
    render(<AssetPicker value="" onChange={onChange} variant="compact" />);
    const input = q<HTMLInputElement>('asset-picker-url-input')!;
    setValue(input, 'https://example.com/vid.mp4');
    // React delegates onBlur via the bubbling 'focusout' event (blur itself doesn't bubble).
    act(() => { input.dispatchEvent(new FocusEvent('focusout', { bubbles: true })); });
    expect(onChange).toHaveBeenCalledWith('https://example.com/vid.mp4');
  });

  test('mediaType="video" sets file input accept to video/* and mediaType="any" omits accept', () => {
    render(<AssetPicker value="" onChange={vi.fn()} mediaType="video" />);
    expect(q<HTMLInputElement>('asset-picker-file-input')!.accept).toBe('video/*');
    unmount();

    render(<AssetPicker value="" onChange={vi.fn()} mediaType="any" />);
    expect(q<HTMLInputElement>('asset-picker-file-input')!.accept).toBe('');
  });
});
