import { describe, expect, test, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
import NetworkSettingsCard from './NetworkSettingsCard.svelte';

const getNetworkSettings = vi.fn();
const updateNetworkSettings = vi.fn();

vi.mock('$lib/api/admin', () => ({
  getNetworkSettings: () => getNetworkSettings(),
  updateNetworkSettings: (hops: number) => updateNetworkSettings(hops)
}));

vi.mock('$lib/stores/toast.svelte', () => ({ pushToast: vi.fn() }));

function settings(over: Record<string, unknown> = {}) {
  return {
    trusted_proxy_hops: 1,
    max_hops: 10,
    detected_client_ip: '198.51.100.7',
    forwarded_chain: '198.51.100.7',
    remote_addr: '172.18.0.1:40000',
    ...over
  };
}

beforeEach(() => {
  vi.clearAllMocks();
});

describe('NetworkSettingsCard', () => {
  // The detected address is the card's verification affordance — the number
  // is abstract, this is checkable against the machine you're sitting at.
  test('shows the address the current setting resolves to', async () => {
    getNetworkSettings.mockResolvedValue(settings());
    render(NetworkSettingsCard);

    // The address legitimately appears twice — as the detected client and
    // inside the forwarded chain — so wait on the unique label, not the value.
    await screen.findByText('Your address right now');
    expect(screen.getAllByText('198.51.100.7').length).toBeGreaterThan(0);
    expect(screen.getByText('172.18.0.1:40000')).toBeTruthy();
  });

  test('save is inert until the value actually changes', async () => {
    getNetworkSettings.mockResolvedValue(settings({ trusted_proxy_hops: 1 }));
    render(NetworkSettingsCard);

    const save = await screen.findByRole('button', { name: /Save/ });
    expect(save).toBeDisabled();

    const input = screen.getByRole('spinbutton');
    await fireEvent.input(input, { target: { value: '2' } });
    await waitFor(() => expect(save).not.toBeDisabled());
  });

  test('saving sends the new depth and adopts the echoed value', async () => {
    getNetworkSettings.mockResolvedValue(settings({ trusted_proxy_hops: 1 }));
    updateNetworkSettings.mockResolvedValue(
      settings({ trusted_proxy_hops: 2, detected_client_ip: '203.0.113.9' })
    );
    render(NetworkSettingsCard);

    const input = await screen.findByRole('spinbutton');
    await fireEvent.input(input, { target: { value: '2' } });
    await fireEvent.click(screen.getByRole('button', { name: /Save/ }));

    await waitFor(() => expect(updateNetworkSettings).toHaveBeenCalledWith(2));
    // The recomputed address proves the change took effect on this request.
    expect(await screen.findByText('203.0.113.9')).toBeTruthy();
  });

  // Counting proxies is the operator's job and the hint is how they do it
  // without guessing.
  test('hints the likely depth when it disagrees with the arriving chain', async () => {
    getNetworkSettings.mockResolvedValue(
      settings({ trusted_proxy_hops: 1, forwarded_chain: '198.51.100.7, 203.0.113.50' })
    );
    render(NetworkSettingsCard);

    expect(await screen.findByText(/arrived with 2 forwarded addresses/)).toBeTruthy();
  });

  test('no hint when the setting already matches the chain length', async () => {
    getNetworkSettings.mockResolvedValue(
      settings({ trusted_proxy_hops: 1, forwarded_chain: '198.51.100.7' })
    );
    render(NetworkSettingsCard);

    await screen.findByText('Your address right now');
    expect(screen.queryByText(/arrived with/)).toBeNull();
  });

  test('states the mis-set risk rather than only exposing a number', async () => {
    getNetworkSettings.mockResolvedValue(settings());
    render(NetworkSettingsCard);

    expect(await screen.findByText(/Count your proxies/)).toBeTruthy();
  });

  test('offers a retry when loading fails', async () => {
    getNetworkSettings.mockRejectedValue(new Error('boom'));
    render(NetworkSettingsCard);

    const retry = await screen.findByRole('button', { name: 'Try again' });
    getNetworkSettings.mockResolvedValue(settings());
    await fireEvent.click(retry);
    await screen.findByText('Your address right now');
  });
});
