/**
 * Tests for TwoFactorVerify Component
 * FE-TEST-005: Test two-factor verification component
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TwoFactorVerify } from './TwoFactorVerify';
import { twoFactorService } from '@/services/2fa-service';
import { useToast } from '@/hooks/useToast';

// Mock dependencies
vi.mock('@/services/2fa-service', () => ({
  twoFactorService: {
    verify: vi.fn(),
  },
}));

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

describe('TwoFactorVerify', () => {
  const mockOnSuccess = vi.fn();
  const mockOnCancel = vi.fn();
  const mockToast = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useToast).mockReturnValue({
      toast: mockToast,
    } as any);
  });

  it('should render two-factor verification form', () => {
    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    expect(screen.getByText('Two-Factor Authentication')).toBeInTheDocument();
    expect(
      screen.getByText('Enter the code from your authenticator app'),
    ).toBeInTheDocument();
    expect(screen.getByLabelText('Verification Code')).toBeInTheDocument();
  });

  it('should allow entering verification code', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    await user.type(codeInput, '123456');

    expect(codeInput).toHaveValue('123456');
  });

  it('should only allow numeric input', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    await user.type(codeInput, 'abc123def456');

    expect(codeInput).toHaveValue('123456');
  });

  it('should limit code to 6 digits', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    await user.type(codeInput, '1234567890');

    expect(codeInput).toHaveValue('123456');
  });

  it('should verify code on submit', async () => {
    const user = userEvent.setup();
    vi.mocked(twoFactorService.verify).mockResolvedValue(true);

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    const verifyButton = screen.getByRole('button', { name: /verify/i });

    await user.type(codeInput, '123456');
    await user.click(verifyButton);

    await waitFor(() => {
      expect(twoFactorService.verify).toHaveBeenCalledWith('123456');
      expect(mockOnSuccess).toHaveBeenCalledWith('123456');
    });
  });

  it('should show error for invalid code', async () => {
    const user = userEvent.setup();
    vi.mocked(twoFactorService.verify).mockResolvedValue(false);

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    const verifyButton = screen.getByRole('button', { name: /verify/i });

    await user.type(codeInput, '123456');
    await user.click(verifyButton);

    await waitFor(() => {
      expect(
        screen.getByText(/invalid verification code/i),
      ).toBeInTheDocument();
    });
  });

  it('should show error on verification failure', async () => {
    const user = userEvent.setup();
    vi.mocked(twoFactorService.verify).mockRejectedValue(
      new Error('Verification failed'),
    );

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    const verifyButton = screen.getByRole('button', { name: /verify/i });

    await user.type(codeInput, '123456');
    await user.click(verifyButton);

    await waitFor(() => {
      expect(screen.getByText('Verification failed')).toBeInTheDocument();
      expect(mockToast).toHaveBeenCalled();
    });
  });

  it('should show loading state during verification', async () => {
    const user = userEvent.setup();
    vi.mocked(twoFactorService.verify).mockImplementation(
      () => new Promise((resolve) => setTimeout(() => resolve(true), 100)),
    );

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const codeInput = screen.getByLabelText('Verification Code');
    const verifyButton = screen.getByRole('button', { name: /verify/i });

    await user.type(codeInput, '123456');
    await user.click(verifyButton);

    expect(screen.getByText('Verifying...')).toBeInTheDocument();
    expect(verifyButton).toBeDisabled();
  });

  it('should switch to backup code mode', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const useBackupLink = screen.getByText(/use a backup code/i);
    await user.click(useBackupLink);

    expect(screen.getByLabelText('Backup Code')).toBeInTheDocument();
    expect(screen.queryByLabelText('Verification Code')).not.toBeInTheDocument();
  });

  it('should switch back to regular code mode', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    // Switch to backup code
    const useBackupLink = screen.getByText(/use a backup code/i);
    await user.click(useBackupLink);

    // Switch back
    const useAuthLink = screen.getByText(/use authenticator code instead/i);
    await user.click(useAuthLink);

    expect(screen.getByLabelText('Verification Code')).toBeInTheDocument();
    expect(screen.queryByLabelText('Backup Code')).not.toBeInTheDocument();
  });

  it('should verify backup code', async () => {
    const user = userEvent.setup();
    vi.mocked(twoFactorService.verify).mockResolvedValue(true);

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    // Switch to backup code
    const useBackupLink = screen.getByText(/use a backup code/i);
    await user.click(useBackupLink);

    const backupInput = screen.getByLabelText('Backup Code');
    const verifyButton = screen.getByRole('button', { name: /verify/i });

    await user.type(backupInput, 'backup-code-123');
    await user.click(verifyButton);

    await waitFor(() => {
      expect(twoFactorService.verify).toHaveBeenCalledWith('backup-code-123');
      expect(mockOnSuccess).toHaveBeenCalledWith('backup-code-123');
    });
  });

  it('should call onCancel when cancel button is clicked', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const cancelButton = screen.getByRole('button', { name: /cancel/i });
    await user.click(cancelButton);

    expect(mockOnCancel).toHaveBeenCalled();
  });

  it('should disable verify button when no code entered', () => {
    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const verifyButton = screen.getByRole('button', { name: /verify/i });
    expect(verifyButton).toBeDisabled();
  });

  it('should show error when trying to verify without code', async () => {
    const user = userEvent.setup();

    render(
      <TwoFactorVerify onSuccess={mockOnSuccess} onCancel={mockOnCancel} />,
    );

    const verifyButton = screen.getByRole('button', { name: /verify/i });
    // Button should be disabled, but let's test the error handling
    // by manually triggering the handler
    const codeInput = screen.getByLabelText('Verification Code');
    fireEvent.change(codeInput, { target: { value: '' } });

    // The button should remain disabled
    expect(verifyButton).toBeDisabled();
  });
});

