/**
 * @vitest-environment happy-dom
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import SettingsPage, { deepMerge } from './SettingsPage';
import { mockServerConfig, mockServerInfo } from '../test/fixtures';
import { api } from '../api/client';
import { clearStoredAuth, getStoredAuth, setStoredAuth } from '../api/auth';

function renderSettings() {
  return render(<SettingsPage />);
}

describe('deepMerge', () => {
  it('preserves base keys not present in override', () => {
    const base = { a: 1, nested: { keep: true, old: 'x' } };
    const result = deepMerge(base, { nested: { old: 'y' } });
    expect(result.a).toBe(1);
    expect(result.nested.keep).toBe(true);
    expect(result.nested.old).toBe('y');
  });

  it('replaces scalar values from override', () => {
    const base = { port: 8989, pool: { host: 'a', port: 3333 } };
    const result = deepMerge(base, { port: 8080, pool: { host: 'b' } });
    expect(result.port).toBe(8080);
    expect(result.pool.host).toBe('b');
    expect(result.pool.port).toBe(3333);
  });
});

describe('SettingsPage (Calibrate)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    sessionStorage.clear();
    vi.spyOn(api, 'getConfig').mockResolvedValue(mockServerConfig());
    vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo);
    vi.spyOn(api, 'updateConfig').mockImplementation(async (cfg) => cfg as ReturnType<typeof mockServerConfig>);
    vi.spyOn(api, 'createUser').mockResolvedValue({ success: true });
  });

  afterEach(() => {
    cleanup();
  });

  it('shows loading state then Calibrate heading', async () => {
    renderSettings();
    expect(screen.getByRole('heading', { level: 1, name: 'Calibrate' })).toBeInTheDocument();
    expect(screen.getByText('Loading server calibration...')).toBeInTheDocument();
    expect(await screen.findByText('CONTROL SERVER · LOCAL HOST')).toBeInTheDocument();
    expect(screen.getAllByRole('heading', { level: 1, name: 'Calibrate' })).toHaveLength(1);
  });

  it('renders key section headings and labels', async () => {
    renderSettings();
    expect(await screen.findByText('Control Server')).toBeInTheDocument();
    expect(screen.getByText('Upstream Pool')).toBeInTheDocument();
    expect(screen.getByText('Fleet Payout Wallet')).toBeInTheDocument();
    expect(screen.getByText('Access Control')).toBeInTheDocument();
    expect(screen.getByText('Fleet Security')).toBeInTheDocument();
    expect(screen.getByText('Listen Port')).toBeInTheDocument();
    expect(screen.getByText(/Public URL \(LAN\)/i)).toBeInTheDocument();
    expect(screen.getByText(/XMR Address/i)).toBeInTheDocument();
    expect(screen.getByDisplayValue('8080')).toBeInTheDocument();
  });

  it('shows detected LAN endpoints banner', async () => {
    renderSettings();
    expect(await screen.findByText('DETECTED LAN ENDPOINTS')).toBeInTheDocument();
    expect(screen.getByText(mockServerInfo.suggested_url!)).toBeInTheDocument();
    expect(screen.getByText(/IPs on this host:/)).toBeInTheDocument();
  });

  it('saves calibration via updateConfig API', async () => {
    const updateSpy = vi.spyOn(api, 'updateConfig');
    renderSettings();
    await screen.findByRole('button', { name: 'Save Calibration' });
    await userEvent.setup().click(screen.getByRole('button', { name: 'Save Calibration' }));
    await waitFor(() => {
      expect(updateSpy).toHaveBeenCalled();
    });
    expect(await screen.findByText('Calibration saved — control server updated.')).toBeInTheDocument();
  });

  it('applies best defaults to public URL from server info', async () => {
    vi.spyOn(api, 'getConfig').mockResolvedValue(
      mockServerConfig({ server: { public_url: '' } })
    );
    renderSettings();
    await screen.findByRole('button', { name: 'Use best defaults' });
    const publicUrlInput = screen.getByPlaceholderText(mockServerInfo.suggested_url!) as HTMLInputElement;
    expect(publicUrlInput.value).toBe('');
    await userEvent.setup().click(screen.getByRole('button', { name: 'Use best defaults' }));
    expect(
      await screen.findByText(/Best defaults applied.*Save Calibration/i)
    ).toBeInTheDocument();
    expect(publicUrlInput.value).toBe(mockServerInfo.suggested_url);
  });

  it('stores browser session credentials', async () => {
    renderSettings();
    const accessSection = (await screen.findByText('Access Control')).closest('.settings-section') as HTMLElement;
    const user = userEvent.setup();
    const sessionUser = within(accessSection).getAllByPlaceholderText('admin')[0];
    const sessionPass = accessSection.querySelectorAll('input[type="password"]')[0] as HTMLInputElement;
    await user.type(sessionUser, 'admin');
    await user.type(sessionPass, 'secret-pass');
    await user.click(within(accessSection).getByRole('button', { name: 'Save session login' }));
    expect(getStoredAuth()).toBeTruthy();
    expect(await screen.findByText(/Session login saved/i)).toBeInTheDocument();
  });

  it('clears browser session on logout', async () => {
    setStoredAuth('admin', 'secret-pass');
    renderSettings();
    await screen.findByText('Session active');
    await userEvent.setup().click(screen.getByRole('button', { name: 'Clear session' }));
    expect(getStoredAuth()).toBeNull();
    expect(await screen.findByText('Session login cleared.')).toBeInTheDocument();
    clearStoredAuth();
  });

  it('adds dashboard user via createUser API', async () => {
    const createSpy = vi.spyOn(api, 'createUser');
    renderSettings();
    const accessSection = (await screen.findByText('Access Control')).closest('.settings-section') as HTMLElement;
    const user = userEvent.setup();
    const newUserInput = within(accessSection).getAllByPlaceholderText('admin')[1];
    const newPassInput = within(accessSection).getByPlaceholderText('••••••••');
    await user.type(newUserInput, 'operator');
    await user.type(newPassInput, 'op-pass-123');
    await user.click(within(accessSection).getByRole('button', { name: 'Add User' }));
    await waitFor(() => {
      expect(createSpy).toHaveBeenCalledWith('operator', 'op-pass-123');
    });
    expect(await screen.findByText('User "operator" added successfully!')).toBeInTheDocument();
  });

  it('describes first-run admin credentials in Access Control help', async () => {
    renderSettings();
    expect(
      await screen.findByText(/first server start, credentials are printed once in the server console/i)
    ).toBeInTheDocument();
    expect(screen.getByText(/admin/i)).toBeInTheDocument();
  });
});
