import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { BoxUserCard } from '../../src/components/machine/box-user-card';

const { mockUseQuery, fetchSpy } = vi.hoisted(() => ({
  mockUseQuery: vi.fn(),
  fetchSpy: vi.fn(),
}));

vi.mock('convex/react', () => ({
  useQuery: mockUseQuery,
}));

vi.mock('sonner', () => ({
  toast: { error: vi.fn(), success: vi.fn() },
}));

describe('BoxUserCard', () => {
  beforeEach(() => {
    vi.stubGlobal('fetch', fetchSpy);
    fetchSpy.mockResolvedValue(
      new Response(JSON.stringify({ hasPassword: true, applied: true })),
    );
  });
  afterEach(() => {
    cleanup();
    vi.unstubAllGlobals();
    vi.clearAllMocks();
  });

  it('shows the identity line and not-set state', () => {
    mockUseQuery.mockReturnValue({ hasPassword: false });
    render(<BoxUserCard username='Gabriel' />);
    expect(screen.getByText('gabriel@gabriel-box')).toBeInTheDocument();
    expect(screen.getByText('Not set')).toBeInTheDocument();
    expect(screen.getByText('Set password')).toBeInTheDocument();
    expect(screen.queryByText('Remove password')).not.toBeInTheDocument();
  });

  it('offers change/remove when a password is set', () => {
    mockUseQuery.mockReturnValue({ hasPassword: true });
    render(<BoxUserCard username='gabriel' />);
    expect(screen.getByText('Set')).toBeInTheDocument();
    expect(screen.getByText('Change password')).toBeInTheDocument();
    expect(screen.getByText('Remove password')).toBeInTheDocument();
  });

  it('blocks submit on mismatched confirmation', () => {
    mockUseQuery.mockReturnValue({ hasPassword: false });
    render(<BoxUserCard username='gabriel' />);
    fireEvent.change(screen.getByLabelText('New password'), {
      target: { value: 'longenough' },
    });
    fireEvent.change(screen.getByLabelText('Confirm'), {
      target: { value: 'different1' },
    });
    expect(screen.getByText('Passwords do not match.')).toBeInTheDocument();
    expect(screen.getByText('Set password').closest('button')!.disabled).toBe(
      true,
    );
    expect(fetchSpy).not.toHaveBeenCalled();
  });

  it('posts the password and shows the restart note when not applied live', async () => {
    mockUseQuery.mockReturnValue({ hasPassword: false });
    fetchSpy.mockResolvedValue(
      new Response(JSON.stringify({ hasPassword: true, applied: false })),
    );
    render(<BoxUserCard username='gabriel' />);
    fireEvent.change(screen.getByLabelText('New password'), {
      target: { value: 'longenough' },
    });
    fireEvent.change(screen.getByLabelText('Confirm'), {
      target: { value: 'longenough' },
    });
    fireEvent.click(screen.getByText('Set password'));
    expect(
      await screen.findByText(/takes effect the next time it starts/),
    ).toBeInTheDocument();
    expect(fetchSpy).toHaveBeenCalledWith(
      '/api/box/set-password',
      expect.objectContaining({ method: 'POST' }),
    );
  });
});
