/**
 * @vitest-environment happy-dom
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { downloadAuthedFile, downloadApiFile } from './download';
import { setStoredAuth } from './auth';

describe('downloadAuthedFile', () => {
  let fetchMock: ReturnType<typeof vi.fn>;
  let clickMock: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    sessionStorage.clear();
    fetchMock = vi.fn();
    vi.stubGlobal('fetch', fetchMock);
    clickMock = vi.fn();
    vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(clickMock);
  });

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

  it('normalizes relative paths under /api/v1', async () => {
    setStoredAuth('user', 'pass');
    fetchMock.mockResolvedValueOnce(new Response(new Blob(['data']), { status: 200 }));

    await downloadAuthedFile('/builds/b1/download', 'agent.exe');

    const [url, init] = fetchMock.mock.calls[0];
    expect(url).toBe('/api/v1/builds/b1/download');
    expect((init.headers as Record<string, string>).Authorization).toBe(`Basic ${btoa('user:pass')}`);
    expect(clickMock).toHaveBeenCalled();
  });

  it('leaves full /api/v1 paths unchanged', async () => {
    fetchMock.mockResolvedValueOnce(new Response(new Blob(['x']), { status: 200 }));

    await downloadAuthedFile('/api/v1/builds/b2/uninstall', 'uninstall.bat');

    expect(fetchMock.mock.calls[0][0]).toBe('/api/v1/builds/b2/uninstall');
  });

  it('prefixes bare paths without leading slash', async () => {
    fetchMock.mockResolvedValueOnce(new Response(new Blob(['x']), { status: 200 }));

    await downloadAuthedFile('builds/b3/artifact/file.exe', 'file.exe');

    expect(fetchMock.mock.calls[0][0]).toBe('/api/v1/builds/b3/artifact/file.exe');
  });

  it('throws server error body on failure', async () => {
    fetchMock.mockResolvedValueOnce(new Response('forbidden', { status: 403 }));

    await expect(downloadAuthedFile('/builds/x/download', 'x.exe')).rejects.toThrow('forbidden');
  });

  it('throws status fallback when error body empty', async () => {
    fetchMock.mockResolvedValueOnce(new Response('', { status: 500 }));

    await expect(downloadAuthedFile('/builds/x/download', 'x.exe')).rejects.toThrow('Download failed (500)');
  });

  it('downloadApiFile is an alias', () => {
    expect(downloadApiFile).toBe(downloadAuthedFile);
  });
});
