/**
 * @vitest-environment happy-dom
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import { type ReactNode } from 'react';
import { mockAgent, mockServerInfo } from '../test/fixtures';
import { api } from '../api/client';
import { downloadApiFile, downloadAuthedFile } from '../api/download';
import { getStoredAuth } from '../api/auth';
import { useWebSocket } from '../hooks/useWebSocket';
import { DEFAULT_FLEET_FILTERS } from '../help/fleetFilters';

import NeonCard from './NeonCard/NeonCard';
import { HelpTip, FieldHint } from './HelpTip';
import DownloadButton from './DownloadButton';
import AuthDownloadButton from './AuthDownloadButton';
import ErrorBoundary from './ErrorBoundary';
import SessionGate from './SessionGate';
import GaugeRing from './Charts/GaugeRing';
import HashrateChart from './Charts/HashrateChart';
import AgentRemoteActions from './Fleet/AgentRemoteActions';
import AgentListItem from './Fleet/AgentListItem';
import FleetToolbar from './Fleet/FleetToolbar';
import { LanDownloadQR } from './Fleet/LanDownloadQR';
import {
  AlertBanner,
  PoolStatusPanel,
  AIActivityPanel,
  EarningsEstimator,
  FleetHealthCard,
  ContributionBars,
  UnderperformerList,
  OSArchBreakdown,
  LANGroupView,
} from './Fleet/FleetPanels';
import { ForgeLockedHint, ForgeFieldBadge, ForgeSectionHeader } from './Forge/ForgeFieldHints';
import {
  PipelineFlow,
  FleetPipelineStatus,
  ActivityPulse,
  ForgeCalibrateCompare,
  RoadmapGrid,
} from './Visual/VisualComponents';
import MatrixStreamOverlay from './Visual/MatrixStreamOverlay';
import SystemStatusBar from './Visual/SystemStatusBar';
import FleetTopologyMap from './Visual/3D/FleetTopologyMap';
import Layout from './Layout/Layout';
import AmbientBackground from './Ambient/AmbientBackground';
import CursorFire from './Visual/CursorFire';
import MatrixRain from './Layout/MatrixRain';

vi.mock('../hooks/useWebSocket', () => ({
  useWebSocket: vi.fn(),
}));

vi.mock('../context/ForgeContext', () => ({
  useForge: vi.fn(() => ({ forging: false, stage: '' })),
}));

vi.mock('../api/download', () => ({
  downloadApiFile: vi.fn(),
  downloadAuthedFile: vi.fn(),
}));

vi.mock('../api/auth', () => ({
  getStoredAuth: vi.fn(),
  setStoredAuth: vi.fn(),
}));

vi.mock('qrcode', () => ({
  default: {
    toCanvas: vi.fn().mockResolvedValue(undefined),
  },
}));

vi.mock('recharts', async () => {
  const actual = await vi.importActual<typeof import('recharts')>('recharts');
  return {
    ...actual,
    ResponsiveContainer: ({ children }: { children: ReactNode }) => (
      <div data-testid="recharts-responsive">{children}</div>
    ),
  };
});

vi.mock('@react-three/fiber', () => ({
  Canvas: ({ children }: { children: ReactNode }) => <div data-testid="three-canvas">{children}</div>,
  useFrame: () => {},
}));

vi.mock('@react-three/drei', () => ({
  OrbitControls: () => null,
  Stars: () => null,
  Line: () => null,
  Sphere: () => null,
}));

const useWebSocketMock = vi.mocked(useWebSocket);
const downloadApiFileMock = vi.mocked(downloadApiFile);
const downloadAuthedFileMock = vi.mocked(downloadAuthedFile);
const getStoredAuthMock = vi.mocked(getStoredAuth);

function ThrowOnce({ shouldThrow }: { shouldThrow: boolean }) {
  if (shouldThrow) throw new Error('render boom');
  return <span>child ok</span>;
}

describe('NeonCard', () => {
  afterEach(() => cleanup());

  it('renders children with default brass accent and 3d class', () => {
    render(<NeonCard>Inner</NeonCard>);
    const card = screen.getByText('Inner').closest('.neon-card');
    expect(card).toHaveClass('neon-card-brass', 'neon-card-3d');
    expect(card?.querySelector('.neon-card-rim')).toBeTruthy();
  });

  it('applies accent, hud, tilt3d off, and custom className', () => {
    render(
      <NeonCard accent="cyan" hud tilt3d={false} className="extra">
        X
      </NeonCard>
    );
    const card = screen.getByText('X').closest('.neon-card');
    expect(card).toHaveClass('neon-card-cyan', 'hud-corners', 'extra');
    expect(card).not.toHaveClass('neon-card-3d');
  });
});

describe('HelpTip', () => {
  afterEach(() => cleanup());

  it('returns null for unknown field', () => {
    const { container } = render(<HelpTip field="nonexistent_field_xyz" />);
    expect(container.firstChild).toBeNull();
  });

  it('shows help popup on hover for known field', async () => {
    render(<HelpTip field="calibrate_wallet" label="Wallet" />);
    expect(screen.getByRole('button', { name: /Help: calibrate_wallet/ })).toBeInTheDocument();
    await userEvent.setup().hover(screen.getByRole('button'));
    await waitFor(() => {
      expect(screen.getByRole('tooltip')).toHaveTextContent(/Monero payout address/i);
    });
  });

  it('FieldHint export is deprecated no-op', () => {
    const { container } = render(<FieldHint field="calibrate_wallet" />);
    expect(container.firstChild).toBeNull();
  });
});

describe('DownloadButton', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    downloadApiFileMock.mockReset();
  });

  it('downloads on click and shows busy label', async () => {
    downloadApiFileMock.mockImplementation(() => new Promise((r) => setTimeout(r, 50)));
    render(
      <DownloadButton apiPath="/api/x" filename="a.bin">
        Save
      </DownloadButton>
    );
    const btn = screen.getByRole('button', { name: 'Save' });
    await userEvent.setup().click(btn);
    expect(screen.getByRole('button', { name: 'Downloading…' })).toBeDisabled();
    await waitFor(() => expect(downloadApiFileMock).toHaveBeenCalledWith('/api/x', 'a.bin'));
  });

  it('alerts on download failure', async () => {
    downloadApiFileMock.mockRejectedValue(new Error('network down'));
    const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => {});
    render(
      <DownloadButton apiPath="/api/x" filename="a.bin">
        Save
      </DownloadButton>
    );
    await userEvent.setup().click(screen.getByRole('button'));
    await waitFor(() => expect(alertSpy).toHaveBeenCalledWith('network down'));
    alertSpy.mockRestore();
  });
});

describe('AuthDownloadButton', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    downloadAuthedFileMock.mockReset();
  });

  it('calls downloadAuthedFile and shows ellipsis while busy', async () => {
    downloadAuthedFileMock.mockImplementation(() => new Promise((r) => setTimeout(r, 30)));
    render(
      <AuthDownloadButton apiPath="/api/build/1" filename="b.exe">
        DL
      </AuthDownloadButton>
    );
    await userEvent.setup().click(screen.getByRole('button', { name: 'DL' }));
    expect(screen.getByRole('button', { name: '…' })).toBeDisabled();
    await waitFor(() =>
      expect(downloadAuthedFileMock).toHaveBeenCalledWith('/api/build/1', 'b.exe')
    );
  });
});

describe('ErrorBoundary', () => {
  afterEach(() => cleanup());

  it('renders children when no error', () => {
    render(
      <ErrorBoundary>
        <ThrowOnce shouldThrow={false} />
      </ErrorBoundary>
    );
    expect(screen.getByText('child ok')).toBeInTheDocument();
  });

  it('shows fallback UI and clears error on retry', async () => {
    let throwNow = true;
    function MaybeThrow() {
      if (throwNow) throw new Error('render boom');
      return <span>child ok</span>;
    }
    render(
      <ErrorBoundary>
        <MaybeThrow />
      </ErrorBoundary>
    );
    expect(screen.getByRole('heading', { name: 'Something failed to render' })).toBeInTheDocument();
    throwNow = false;
    await userEvent.setup().click(screen.getByRole('button', { name: 'Retry' }));
    expect(screen.getByText('child ok')).toBeInTheDocument();
  });

  it('uses custom fallback when provided', () => {
    render(
      <ErrorBoundary fallback={<p>Custom fail</p>}>
        <ThrowOnce shouldThrow />
      </ErrorBoundary>
    );
    expect(screen.getByText('Custom fail')).toBeInTheDocument();
  });
});

describe('SessionGate', () => {
  afterEach(() => {
    cleanup();
    vi.unstubAllGlobals();
  });

  beforeEach(() => {
    getStoredAuthMock.mockReturnValue(null);
  });

  it('shows login form when unauthenticated', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({ ok: false })
    );
    render(
      <SessionGate>
        <div>protected</div>
      </SessionGate>
    );
    await waitFor(() => {
      expect(screen.getByRole('heading', { name: 'AetherForge' })).toBeInTheDocument();
    });
    expect(screen.queryByText('protected')).not.toBeInTheDocument();
  });

  it('renders children when stored auth validates', async () => {
    getStoredAuthMock.mockReturnValue('dGVzdA==');
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true }));
    render(
      <SessionGate>
        <div>protected</div>
      </SessionGate>
    );
    await waitFor(() => expect(screen.getByText('protected')).toBeInTheDocument());
  });
});

describe('GaugeRing', () => {
  afterEach(() => cleanup());

  it('renders label and percentage for percent-scale max', () => {
    render(<GaugeRing value={42} max={100} label="CPU" sublabel="load" />);
    expect(screen.getByText('42%')).toBeInTheDocument();
    expect(screen.getByText('CPU')).toBeInTheDocument();
    expect(screen.getByText('load')).toBeInTheDocument();
  });

  it('shows raw value in center while ring arc clamps to 0–100%', () => {
    const { rerender, container } = render(<GaugeRing value={-10} max={100} label="X" />);
    expect(screen.getByText('-10%')).toBeInTheDocument();
    const fill = container.querySelector('.gauge-ring-fill') as SVGCircleElement;
    expect(fill.getAttribute('stroke-dashoffset')).toBe(String(2 * Math.PI * 42));
    rerender(<GaugeRing value={200} max={100} label="X" />);
    expect(screen.getByText('200%')).toBeInTheDocument();
    expect((container.querySelector('.gauge-ring-fill') as SVGCircleElement).getAttribute('stroke-dashoffset')).toBe('0');
  });
});

describe('HashrateChart', () => {
  afterEach(() => cleanup());

  it('shows empty state when data is empty', () => {
    render(<HashrateChart data={[]} title="Fleet Hash" />);
    expect(screen.getByText('Fleet Hash')).toBeInTheDocument();
    expect(screen.getByText(/Awaiting signal from fleet/i)).toBeInTheDocument();
  });

  it('renders chart with data points', () => {
    render(
      <HashrateChart
        data={[
          { time: '12:00', value: 1500 },
          { time: '12:01', value: 2000 },
        ]}
        title="Live"
      />
    );
    expect(screen.getByText('Live')).toBeInTheDocument();
    expect(screen.getByText('● LIVE')).toBeInTheDocument();
    expect(screen.getByTestId('recharts-responsive')).toBeInTheDocument();
  });
});

describe('AgentRemoteActions', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    vi.spyOn(api, 'listBuilds').mockResolvedValue([]);
    vi.spyOn(api, 'sendAgentCommand').mockResolvedValue({ success: true });
  });

  it('compact mode disables actions when offline', () => {
    render(
      <AgentRemoteActions agent={mockAgent({ status: 'offline' })} compact online={false} />
    );
    expect(screen.getByRole('button', { name: 'Pause' })).toBeDisabled();
    expect(screen.getByRole('button', { name: 'Resume' })).toBeDisabled();
  });

  it('compact dispatches pause when online', async () => {
    const cmd = vi.spyOn(api, 'sendAgentCommand').mockResolvedValue({ success: true });
    render(
      <AgentRemoteActions
        agent={mockAgent({ id: 'a1', status: 'online' })}
        compact
        online
      />
    );
    await userEvent.setup().click(screen.getByRole('button', { name: 'Pause' }));
    await waitFor(() => expect(cmd).toHaveBeenCalledWith('a1', 'pause', {}));
  });

  it('full panel shows Target heading and recon section', async () => {
    render(<AgentRemoteActions agent={mockAgent({ name: 'Node A' })} online />);
    await waitFor(() => {
      expect(screen.getByRole('heading', { name: 'Target: Node A' })).toBeInTheDocument();
    });
    expect(screen.getByRole('heading', { name: 'Recon & Intel' })).toBeInTheDocument();
    expect(screen.getByText(/Awaiting telemetry/i)).toBeInTheDocument();
  });

  it('disables recon buttons when agent offline', async () => {
    render(<AgentRemoteActions agent={mockAgent({ status: 'offline' })} online={false} />);
    await waitFor(() => {
      expect(screen.getByRole('heading', { name: /Target:/i })).toBeInTheDocument();
    });
    expect(screen.getByRole('button', { name: 'Screenshot' })).toBeDisabled();
  });
});

describe('AgentListItem', () => {
  afterEach(() => cleanup());

  const baseProps = {
    agent: mockAgent({ name: 'List Node', tags: ['rack'], notes: 'a'.repeat(90) }),
    selected: false,
    expanded: false,
    onToggleExpand: vi.fn(),
    onSelect: vi.fn(),
  };

  it('renders name, status, and click hint when collapsed', () => {
    render(<AgentListItem {...baseProps} />);
    expect(screen.getByText('List Node')).toBeInTheDocument();
    expect(screen.getByText('online')).toBeInTheDocument();
    expect(screen.getByText('click for details')).toBeInTheDocument();
    expect(screen.getByText('rack')).toBeInTheDocument();
  });

  it('truncates long notes preview when collapsed', () => {
    render(<AgentListItem {...baseProps} />);
    const preview = screen.getByText(/…$/);
    expect(preview.textContent!.length).toBeLessThanOrEqual(81);
  });

  it('expands meta and remote actions', async () => {
    vi.spyOn(api, 'listBuilds').mockResolvedValue([]);
    render(<AgentListItem {...baseProps} expanded selectable checked onCheck={vi.fn()} />);
    expect(screen.getByText(/Shares:/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Pause' })).toBeInTheDocument();
  });

  it('checkbox stops row navigation', async () => {
    const onCheck = vi.fn();
    render(<AgentListItem {...baseProps} selectable onCheck={onCheck} />);
    await userEvent.setup().click(screen.getByRole('checkbox'));
    expect(onCheck).toHaveBeenCalledWith(true);
    expect(baseProps.onSelect).not.toHaveBeenCalled();
  });
});

describe('FleetToolbar', () => {
  afterEach(() => cleanup());

  const filters = { ...DEFAULT_FLEET_FILTERS };
  const agents = [
    mockAgent({ tags: ['prod'], ip: '10.0.0.5' }),
    mockAgent({ id: 'a2', name: 'B', tags: ['dev'], ip: '10.0.1.2' }),
  ];

  it('renders search and filter controls', () => {
    render(
      <FleetToolbar
        agents={agents}
        filters={filters}
        onChange={vi.fn()}
        selectedCount={0}
        onBulkAction={vi.fn()}
        bulkBusy={false}
      />
    );
    expect(screen.getByPlaceholderText('Search name, IP, notes, tags…')).toBeInTheDocument();
    expect(screen.getByText('All tags')).toBeInTheDocument();
  });

  it('shows bulk bar when agents selected', async () => {
    const onBulk = vi.fn();
    render(
      <FleetToolbar
        agents={agents}
        filters={filters}
        onChange={vi.fn()}
        selectedCount={2}
        onBulkAction={onBulk}
        bulkBusy={false}
      />
    );
    expect(screen.getByText('2 selected')).toBeInTheDocument();
    await userEvent.setup().click(screen.getByRole('button', { name: 'Pause' }));
    expect(onBulk).toHaveBeenCalledWith('pause');
  });

  it('shows select-all filtered when props provided', () => {
    const onSelectAll = vi.fn();
    render(
      <FleetToolbar
        agents={agents}
        filters={filters}
        onChange={vi.fn()}
        selectedCount={0}
        onBulkAction={vi.fn()}
        bulkBusy={false}
        filteredCount={3}
        onSelectAllFiltered={onSelectAll}
      />
    );
    fireEvent.click(screen.getByRole('button', { name: 'Select all filtered (3)' }));
    expect(onSelectAll).toHaveBeenCalled();
  });
});

describe('LanDownloadQR', () => {
  afterEach(() => cleanup());

  it('returns null without url', () => {
    const { container } = render(<LanDownloadQR url="" />);
    expect(container.firstChild).toBeNull();
  });

  it('renders canvas when url provided', () => {
    const { container } = render(<LanDownloadQR url="http://192.168.1.5:8080/get" />);
    expect(container.querySelector('canvas')).toBeTruthy();
  });
});

describe('FleetPanels', () => {
  afterEach(() => cleanup());

  it('AlertBanner returns null when empty', () => {
    const { container } = render(<AlertBanner alerts={[]} />);
    expect(container.firstChild).toBeNull();
  });

  it('AlertBanner shows up to five alerts', () => {
    const alerts = [
      { id: '1', level: 'warning', type: 'offline', message: 'Node down', timestamp: '2026-05-30T12:00:00Z' },
    ];
    render(<AlertBanner alerts={alerts} />);
    expect(screen.getByText('OFFLINE')).toBeInTheDocument();
    expect(screen.getByText('Node down')).toBeInTheDocument();
  });

  it('PoolStatusPanel empty hint', () => {
    render(<PoolStatusPanel pools={[]} />);
    expect(screen.getByRole('heading', { name: /Pool Stratum Status/i })).toBeInTheDocument();
    expect(screen.getByText(/No forged pool connections yet/i)).toBeInTheDocument();
  });

  it('AIActivityPanel maps agent names', () => {
    render(
      <AIActivityPanel
        entries={[
          {
            agent_id: 'agent-001-uuid',
            last_tool: 'pause',
            last_success: true,
          },
        ]}
        agentNames={{ 'agent-001-uuid': 'Friendly' }}
      />
    );
    expect(screen.getByText('Friendly')).toBeInTheDocument();
  });

  it('FleetHealthCard shows score and label', () => {
    render(
      <FleetHealthCard
        health={{
          score: 88,
          color: 'green',
          label: 'NOMINAL',
          sentence: 'All good',
          issues: [],
        }}
      />
    );
    expect(screen.getByText('NOMINAL')).toBeInTheDocument();
    expect(screen.getByText('88')).toBeInTheDocument();
    expect(screen.getByText('All good')).toBeInTheDocument();
  });

  it('ContributionBars returns null when empty', () => {
    const { container } = render(<ContributionBars bars={[]} />);
    expect(container.firstChild).toBeNull();
  });

  it('ContributionBars renders rows', () => {
    render(
      <ContributionBars
        bars={[{ id: '1', name: 'A', hashrate: 1000, pct: 50 }]}
        xmrPerDay={0.01}
        xmrPrice={100}
      />
    );
    expect(screen.getByRole('heading', { name: /Contribution Map/i })).toBeInTheDocument();
    expect(screen.getByText('A')).toBeInTheDocument();
  });

  it('OSArchBreakdown returns null when empty', () => {
    const { container } = render(<OSArchBreakdown platforms={[]} />);
    expect(container.firstChild).toBeNull();
  });

  it('LANGroupView needs at least two groups', () => {
    const { container } = render(
      <LANGroupView groups={[{ subnet: '10.0.0.0/24', agents: [mockAgent()] }]} />
    );
    expect(container.firstChild).toBeNull();
  });

  it('EarningsEstimator returns null when hashrate zero', () => {
    const { container } = render(<EarningsEstimator hashrate={0} xmrPrice={100} />);
    expect(container.firstChild).toBeNull();
  });

  it('UnderperformerList restart calls bulk API', async () => {
    const agents = [mockAgent({ id: 'u1', name: 'Slow' })];
    const bulk = vi.spyOn(api, 'sendBulkCommand').mockResolvedValue({ success: true, sent: 1, failed: 0, action: 'restart' });
    render(<UnderperformerList underperformers={agents} medianHashrate={10000} />);
    await userEvent.setup().click(screen.getByRole('button', { name: /Restart All/i }));
    await waitFor(() => expect(bulk).toHaveBeenCalledWith(['u1'], 'restart'));
    expect(await screen.findByText(/Restart sent to 1/i)).toBeInTheDocument();
  });
});

describe('ForgeFieldHints', () => {
  afterEach(() => cleanup());

  it('ForgeLockedHint null without meta', () => {
    const { container } = render(<ForgeLockedHint />);
    expect(container.firstChild).toBeNull();
  });

  it('ForgeFieldBadge shows baked label', () => {
    render(<ForgeFieldBadge meta={{ badge: 'baked' }} />);
    expect(screen.getByText('⛏ baked')).toBeInTheDocument();
  });

  it('ForgeSectionHeader renders title and badge', () => {
    render(
      <ForgeSectionHeader title="Pool" description="Pool settings" badge="server-only" />
    );
    expect(screen.getByRole('heading', { name: 'Pool' })).toBeInTheDocument();
    expect(screen.getByText(/server/i)).toBeInTheDocument();
  });
});

describe('VisualComponents', () => {
  afterEach(() => cleanup());

  it('PipelineFlow highlights active step', () => {
    render(<PipelineFlow activeStep="forge" />);
    expect(document.querySelector('.pipeline-active')).toBeTruthy();
  });

  it('FleetPipelineStatus shows step labels', () => {
    render(
      <FleetPipelineStatus
        hasBuilds
        agentCount={2}
        onlineCount={1}
        hasHashrate
        hasShares={false}
      />
    );
    expect(screen.getByText('Forged')).toBeInTheDocument();
    expect(screen.getByText('Shares')).toBeInTheDocument();
  });

  it('ActivityPulse empty message', () => {
    render(<ActivityPulse items={[]} />);
    expect(screen.getByText(/Awaiting fleet activity/i)).toBeInTheDocument();
  });

  it('ForgeCalibrateCompare links to routes', () => {
    render(
      <MemoryRouter>
        <ForgeCalibrateCompare />
      </MemoryRouter>
    );
    expect(screen.getByText(/Forge — baked into each binary/i)).toBeInTheDocument();
  });

  it('RoadmapGrid lists features', () => {
    render(<RoadmapGrid />);
    expect(document.querySelectorAll('.roadmap-card').length).toBeGreaterThan(0);
  });
});

describe('MatrixStreamOverlay', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    useWebSocketMock.mockReturnValue({
      isConnected: true,
      agents: [],
      recentShares: [],
      fleetAlerts: [],
      poolStatus: [],
      aiActivity: [],
      agentLogs: {},
      commandResults: [],
      latestMessage: null,
    });
  });

  it('returns null when inactive', () => {
    const { container } = render(<MatrixStreamOverlay active={false} onClose={vi.fn()} />);
    expect(container.firstChild).toBeNull();
  });

  it('shows overlay heading when active', () => {
    render(<MatrixStreamOverlay active onClose={vi.fn()} />);
    expect(screen.getByText('RAW_SOCKET_STREAM [ACTIVE]')).toBeInTheDocument();
  });
});

describe('SystemStatusBar', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    vi.spyOn(api, 'healthCheck').mockResolvedValue(undefined);
    vi.spyOn(api, 'listAgents').mockResolvedValue([mockAgent(), mockAgent({ id: 'a2', status: 'offline' })]);
    vi.spyOn(api, 'listBuilds').mockResolvedValue([{ id: 'b1' } as never]);
  });

  it('shows server and fleet pills after poll', async () => {
    render(
      <MemoryRouter>
        <SystemStatusBar />
      </MemoryRouter>
    );
    await waitFor(() => {
      expect(screen.getByText(/SERVER UP/i)).toBeInTheDocument();
    });
    expect(screen.getByText(/FLEET 1\/2 ONLINE/i)).toBeInTheDocument();
    expect(screen.getByText(/1 BUILD/i)).toBeInTheDocument();
  });
});

describe('FleetTopologyMap', () => {
  afterEach(() => cleanup());

  it('renders canvas wrapper for agents', () => {
    render(<FleetTopologyMap agents={[mockAgent()]} />);
    expect(screen.getByTestId('three-canvas')).toBeInTheDocument();
  });
});

describe('AmbientBackground', () => {
  afterEach(() => cleanup());

  it('renders ambient layers and sacred geometry svg', () => {
    const { container } = render(<AmbientBackground />);
    expect(container.querySelector('.ambient-bg')).toBeTruthy();
    expect(container.querySelector('.ambient-sacred-geo')).toBeTruthy();
  });
});

describe('CursorFire', () => {
  afterEach(() => cleanup());

  it('mounts fullscreen canvas', () => {
    const { container } = render(<CursorFire />);
    expect(container.querySelector('canvas')).toBeTruthy();
  });
});

describe('MatrixRain', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    useWebSocketMock.mockReturnValue({
      isConnected: true,
      agents: [],
      recentShares: [],
      fleetAlerts: [],
      poolStatus: [],
      aiActivity: [],
      agentLogs: {},
      commandResults: [],
      latestMessage: null,
    });
  });

  it('renders matrix rain canvas wrapper', () => {
    const { container } = render(<MatrixRain />);
    expect(container.querySelector('canvas')).toBeTruthy();
  });
});

describe('Layout', () => {
  afterEach(() => cleanup());

  beforeEach(() => {
    useWebSocketMock.mockReturnValue({
      isConnected: true,
      agents: [mockAgent()],
      recentShares: [],
      fleetAlerts: [],
      poolStatus: [],
      aiActivity: [],
      agentLogs: {},
      commandResults: [],
      latestMessage: null,
    });
    vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo);
  });

  it('renders nav links and children', async () => {
    render(
      <MemoryRouter initialEntries={['/dashboard']}>
        <Layout>
          <div>page body</div>
        </Layout>
      </MemoryRouter>
    );
    await waitFor(() => {
      expect(screen.getByText('page body')).toBeInTheDocument();
    });
    expect(screen.getByRole('link', { name: /Command Deck/i })).toBeInTheDocument();
    expect(screen.getByRole('link', { name: /Fleet Roster/i })).toBeInTheDocument();
  });
});
