import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { uploadAsset, listAssets, Asset } from './assets';

const CFG = {
  apiUrl: 'https://panel.example.com/api/site-builder',
  siteId: 42,
  csrfToken: 'tok-123',
};

function jsonResponse(body: any, ok = true) {
  return { ok, json: async () => body } as Response;
}

describe('uploadAsset', () => {
  let createObjectURLSpy: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    createObjectURLSpy = vi.fn(() => 'blob:mock-url');
    (URL as any).createObjectURL = createObjectURLSpy;
    vi.stubGlobal('fetch', vi.fn());
    delete (window as any).WHP_CONFIG;
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    delete (window as any).WHP_CONFIG;
  });

  test('without window.WHP_CONFIG returns a blob: URL and does not call fetch', async () => {
    const file = new File(['x'], 'photo.png', { type: 'image/png' });
    const url = await uploadAsset(file);
    expect(url).toBe('blob:mock-url');
    expect(createObjectURLSpy).toHaveBeenCalledWith(file);
    expect(fetch).not.toHaveBeenCalled();
  });

  test('with config, POSTs to upload_asset with X-CSRF-Token and returns data.url', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockResolvedValue(jsonResponse({ success: true, url: '/storage/assets/photo.png' }));

    const file = new File(['x'], 'photo.png', { type: 'image/png' });
    const url = await uploadAsset(file);

    expect(url).toBe('/storage/assets/photo.png');
    expect(fetchMock).toHaveBeenCalledTimes(1);
    const [calledUrl, opts] = fetchMock.mock.calls[0];
    expect(calledUrl).toBe(`${CFG.apiUrl}?action=upload_asset&site_id=${CFG.siteId}`);
    expect(opts.method).toBe('POST');
    expect(opts.headers['X-CSRF-Token']).toBe(CFG.csrfToken);
    expect(opts.body).toBeInstanceOf(FormData);
  });

  test('with config, returns null when the server reports failure', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockResolvedValue(jsonResponse({ success: false }));

    const file = new File(['x'], 'photo.png', { type: 'image/png' });
    expect(await uploadAsset(file)).toBeNull();
  });

  test('with config, returns null when fetch throws', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockRejectedValue(new Error('network down'));

    const file = new File(['x'], 'photo.png', { type: 'image/png' });
    expect(await uploadAsset(file)).toBeNull();
  });
});

describe('listAssets', () => {
  beforeEach(() => {
    vi.stubGlobal('fetch', vi.fn());
    delete (window as any).WHP_CONFIG;
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    delete (window as any).WHP_CONFIG;
  });

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

  test('without window.WHP_CONFIG returns [] and does not call fetch', async () => {
    const assets = await listAssets('image');
    expect(assets).toEqual([]);
    expect(fetch).not.toHaveBeenCalled();
  });

  test('with config, filters by type prefix for the requested mediaType', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockResolvedValue(jsonResponse({ success: true, assets: ASSETS }));

    const images = await listAssets('image');
    expect(images).toEqual([ASSETS[0]]);

    const videos = await listAssets('video');
    expect(videos).toEqual([ASSETS[1]]);

    const [calledUrl] = fetchMock.mock.calls[0];
    expect(calledUrl).toBe(`${CFG.apiUrl}?action=list_assets&site_id=${CFG.siteId}`);
  });

  test('mediaType "any" (or omitted) returns all assets unfiltered', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockResolvedValue(jsonResponse({ success: true, assets: ASSETS }));

    expect(await listAssets('any')).toEqual(ASSETS);
    expect(await listAssets()).toEqual(ASSETS);
  });

  test('with config, returns [] when the server reports failure or a non-array', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockResolvedValue(jsonResponse({ success: false }));
    expect(await listAssets()).toEqual([]);
  });

  test('with config, returns [] when fetch throws', async () => {
    (window as any).WHP_CONFIG = CFG;
    const fetchMock = fetch as unknown as ReturnType<typeof vi.fn>;
    fetchMock.mockRejectedValue(new Error('network down'));
    expect(await listAssets()).toEqual([]);
  });
});
