import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { ReactNode } from 'react';
import { ApiKeyScreen } from './ApiKeyScreen';
import { AuthContext, type AuthContextValue } from '../AuthGate';

// The repurposed screen (#295) is a normal authenticated screen: it loads the machine key from
// `/api/v1/auth/machine-key` and, for local accounts, changes the password via `/api/v1/auth/password`. Its
// self-contained fetch mock (spa-conventions §6) covers only those two endpoints.
function json(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), { headers: { 'Content-Type': 'application/json' }, status });
}

function installFetch(options: { machineKey?: string; passwordStatus?: number } = {}) {
  const { machineKey = 'machine-secret', passwordStatus = 204 } = options;
  return vi.spyOn(window, 'fetch').mockImplementation((input: RequestInfo | URL) => {
    const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
    if (url === '/api/v1/auth/machine-key') {
      return Promise.resolve(json({ apiKey: machineKey }));
    }
    if (url === '/api/v1/auth/password') {
      if (passwordStatus >= 400) {
        return Promise.resolve(
          new Response(JSON.stringify({ status: passwordStatus, title: 'Unauthorized', detail: 'Wrong password.' }), {
            headers: { 'Content-Type': 'application/problem+json' },
            status: passwordStatus
          })
        );
      }
      return Promise.resolve(new Response(null, { status: passwordStatus }));
    }
    return Promise.resolve(json({}, 404));
  });
}

function renderWith(method: string | null, children: ReactNode = <ApiKeyScreen />) {
  const value: AuthContextValue = {
    username: 'admin',
    method,
    signOut: () => {},
    requireLogin: () => {}
  };
  return render(<AuthContext.Provider value={value}>{children}</AuthContext.Provider>);
}

describe('ApiKeyScreen', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
    Object.assign(navigator, { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) } });
  });

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

  it('shows the machine key masked by default, then reveals it', async () => {
    installFetch({ machineKey: 'machine-secret' });

    renderWith('local');

    // The masked code cell renders without leaking the key into the DOM.
    const cell = await screen.findByTestId('machine-key');
    expect(cell.textContent).not.toContain('machine-secret');
    expect(document.body.innerHTML).not.toContain('machine-secret');

    fireEvent.click(screen.getByRole('button', { name: /Reveal/i }));

    await waitFor(() => expect(screen.getByTestId('machine-key').textContent).toBe('machine-secret'));
  });

  it('copies the real machine key regardless of the masked state', async () => {
    installFetch({ machineKey: 'machine-secret' });

    renderWith('local');
    await screen.findByTestId('machine-key');

    fireEvent.click(screen.getByRole('button', { name: /Copy/i }));

    expect(navigator.clipboard.writeText).toHaveBeenCalledWith('machine-secret');
  });

  it('renders the local-password card only for a local account', async () => {
    installFetch();

    renderWith('local');
    expect(await screen.findByText('Local admin password')).toBeTruthy();
  });

  it('hides the local-password card for a non-local (oidc) account', async () => {
    installFetch();

    renderWith('oidc');
    // Machine key card still loads.
    await screen.findByTestId('machine-key');
    expect(screen.queryByText('Local admin password')).toBeNull();
  });

  it('changes the password via /api/v1/auth/password and confirms on success', async () => {
    const fetchSpy = installFetch({ passwordStatus: 204 });

    renderWith('local');
    await screen.findByText('Local admin password');

    fireEvent.change(screen.getByLabelText('Current password'), { target: { value: 'old-pass' } });
    fireEvent.change(screen.getByLabelText('New password'), { target: { value: 'new-pass' } });
    fireEvent.click(screen.getByRole('button', { name: /Change password/i }));

    await waitFor(() => expect(screen.getByText('Password changed.')).toBeTruthy());

    const passwordCall = fetchSpy.mock.calls.find((call) => call[0] === '/api/v1/auth/password');
    expect(passwordCall).toBeTruthy();
    expect((passwordCall?.[1]?.method ?? 'GET').toUpperCase()).toBe('POST');
    expect(JSON.parse(String(passwordCall?.[1]?.body))).toEqual({ currentPassword: 'old-pass', newPassword: 'new-pass' });
  });

  it('surfaces the ProblemDetails detail inline on a wrong-current-password 401', async () => {
    installFetch({ passwordStatus: 401 });

    renderWith('local');
    await screen.findByText('Local admin password');

    fireEvent.change(screen.getByLabelText('Current password'), { target: { value: 'wrong' } });
    fireEvent.change(screen.getByLabelText('New password'), { target: { value: 'new-pass' } });
    fireEvent.click(screen.getByRole('button', { name: /Change password/i }));

    await waitFor(() => expect(screen.getByText('Wrong password.')).toBeTruthy());
  });

  it('never touches the legacy ctv-api-key localStorage slot', async () => {
    installFetch();

    renderWith('local');
    await screen.findByTestId('machine-key');

    expect(window.localStorage.getItem('ctv-api-key')).toBeNull();
  });
});
