import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import { RegisterPage } from './RegisterPage';
import { useAuthStore } from '@/features/auth/store/authStore';
import { useRegister } from '../hooks/useRegister';
import { useUsernameAvailability } from '../hooks/useUsernameAvailability';
import { resendVerificationEmail } from '../services/authService';

// Mock dependencies
vi.mock('@/features/auth/store/authStore', () => ({
  useAuthStore: vi.fn(),
}));

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

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

vi.mock('../services/authService', () => ({
  resendVerificationEmail: vi.fn(),
}));

const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
  const actual = await vi.importActual('react-router-dom');
  return {
    ...actual,
    useNavigate: () => mockNavigate,
    Navigate: ({ to }: { to: string }) => (
      <div data-testid="navigate">Navigate to {to}</div>
    ),
  };
});

const wrapper = ({ children }: { children: React.ReactNode }) => (
  <MemoryRouter>{children}</MemoryRouter>
);

describe('RegisterPage', () => {
  const mockHandleRegister = vi.fn();
  const mockUseRegister = {
    handleRegister: mockHandleRegister,
    loading: false,
    error: null,
    success: false,
  };

  const mockUseUsernameAvailability = {
    available: null,
    checking: false,
  };

  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    vi.mocked(useAuthStore).mockReturnValue({
      isAuthenticated: false,
    } as any);
    vi.mocked(useRegister).mockReturnValue(mockUseRegister);
    vi.mocked(useUsernameAvailability).mockReturnValue(
      mockUseUsernameAvailability,
    );
  });

  afterEach(() => {
    localStorage.clear();
  });

  it('should render register form', () => {
    render(<RegisterPage />, { wrapper });

    expect(screen.getByText('Inscription')).toBeInTheDocument();
    expect(screen.getByText('Créez votre compte')).toBeInTheDocument();
    expect(screen.getByLabelText("Nom d'utilisateur")).toBeInTheDocument();
    expect(screen.getByLabelText('Email')).toBeInTheDocument();
    expect(screen.getByLabelText('Mot de passe')).toBeInTheDocument();
    expect(
      screen.getByLabelText('Confirmer le mot de passe'),
    ).toBeInTheDocument();
    expect(
      screen.getByRole('button', { name: "S'inscrire" }),
    ).toBeInTheDocument();
  });

  it('should redirect to dashboard if already authenticated', () => {
    vi.mocked(useAuthStore).mockReturnValue({
      isAuthenticated: true,
    } as any);

    render(<RegisterPage />, { wrapper });

    expect(screen.getByTestId('navigate')).toBeInTheDocument();
    expect(screen.getByText('Navigate to /dashboard')).toBeInTheDocument();
  });

  it('should validate required fields', async () => {
    render(<RegisterPage />, { wrapper });

    const form = screen
      .getByRole('button', { name: "S'inscrire" })
      .closest('form');
    expect(form).toBeInTheDocument();

    // Try to submit with empty fields
    await act(async () => {
      if (form) {
        form.requestSubmit();
      }
    });

    // Wait a bit for validation to run
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Check if validation errors appear (may not appear immediately in test environment)
    // We'll check if at least one error is present
    const emailInput = screen.getByLabelText('Email');
    expect(emailInput).toBeInTheDocument();

    // The validation should prevent handleRegister from being called
    expect(mockHandleRegister).not.toHaveBeenCalled();
  });

  it('should validate email format', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    const emailInput = screen.getByLabelText('Email');
    await act(async () => {
      await user.type(emailInput, 'invalid-email');
      await user.tab();
    });

    await waitFor(() => {
      expect(screen.getByText('Email invalide')).toBeInTheDocument();
    });
  });

  it('should validate username minimum length', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    const usernameInput = screen.getByLabelText("Nom d'utilisateur");
    await act(async () => {
      await user.type(usernameInput, 'ab');
      await user.tab();
    });

    await waitFor(() => {
      expect(
        screen.getByText(
          "Le nom d'utilisateur doit contenir au moins 3 caractères",
        ),
      ).toBeInTheDocument();
    });
  });

  it('should validate password minimum length', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    const passwordInput = screen.getByLabelText('Mot de passe');
    await act(async () => {
      await user.type(passwordInput, '1234567');
      await user.tab();
    });

    await waitFor(() => {
      expect(
        screen.getByText('Le mot de passe doit contenir au moins 8 caractères'),
      ).toBeInTheDocument();
    });
  });

  it('should validate password confirmation match', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    const passwordInput = screen.getByLabelText('Mot de passe');
    const confirmPasswordInput = screen.getByLabelText(
      'Confirmer le mot de passe',
    );

    await act(async () => {
      await user.type(passwordInput, 'password123');
      await user.type(confirmPasswordInput, 'password456');
      await user.tab();
    });

    await waitFor(() => {
      expect(
        screen.getByText('Les mots de passe ne correspondent pas'),
      ).toBeInTheDocument();
    });
  });

  it('should clear error when user starts typing', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    const emailInput = screen.getByLabelText('Email');

    // Trigger validation error
    await act(async () => {
      await user.click(emailInput);
      await user.tab();
    });

    await waitFor(() => {
      expect(screen.getByText('Email requis')).toBeInTheDocument();
    });

    // Clear error by typing
    await act(async () => {
      await user.type(emailInput, 'test@example.com');
    });

    await waitFor(() => {
      expect(screen.queryByText('Email requis')).not.toBeInTheDocument();
    });
  });

  it('should call handleRegister with form data on valid submission', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);
    render(<RegisterPage />, { wrapper });

    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    // Accept terms
    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    const form = screen
      .getByRole('button', { name: "S'inscrire" })
      .closest('form');
    await act(async () => {
      if (form) {
        await userEvent.click(
          screen.getByRole('button', { name: "S'inscrire" }),
        );
      }
    });

    await waitFor(() => {
      expect(mockHandleRegister).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'testuser',
      });
    });
  });

  it('should display error message when registration fails', () => {
    const error = new Error('Email already exists');
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      error,
    });

    render(<RegisterPage />, { wrapper });

    expect(screen.getByText('Email already exists')).toBeInTheDocument();
  });

  it('should show loading state on button', () => {
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      loading: true,
    });

    render(<RegisterPage />, { wrapper });

    const submitButton = screen.getByRole('button', { name: 'Chargement...' });
    expect(submitButton).toBeDisabled();
    expect(screen.getByText('Chargement...')).toBeInTheDocument();
  });

  it('should display footer link to login', () => {
    render(<RegisterPage />, { wrapper });

    const loginLink = screen.getByText('Déjà un compte ? Se connecter');
    expect(loginLink).toBeInTheDocument();
    expect(loginLink.closest('a')).toHaveAttribute('href', '/login');
  });

  it('should render terms acceptance checkbox', () => {
    render(<RegisterPage />, { wrapper });

    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    expect(checkbox).toBeInTheDocument();
    expect(checkbox).not.toBeChecked();
  });

  it('should display links to terms and privacy policy', () => {
    render(<RegisterPage />, { wrapper });

    const termsLink = screen.getByText("conditions d'utilisation");
    const privacyLink = screen.getByText('politique de confidentialité');

    expect(termsLink).toBeInTheDocument();
    expect(termsLink.closest('a')).toHaveAttribute('href', '/terms');

    expect(privacyLink).toBeInTheDocument();
    expect(privacyLink.closest('a')).toHaveAttribute('href', '/privacy');
  });

  it('should validate terms acceptance on form submission', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    // Fill all fields but don't accept terms
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Wait for validation
    await new Promise((resolve) => setTimeout(resolve, 100));

    await waitFor(() => {
      expect(
        screen.getByText(
          "Vous devez accepter les conditions d'utilisation et la politique de confidentialité",
        ),
      ).toBeInTheDocument();
    });

    // Verify handleRegister was not called
    expect(mockHandleRegister).not.toHaveBeenCalled();
  });

  it('should clear terms error when checkbox is checked', async () => {
    const user = userEvent.setup();
    render(<RegisterPage />, { wrapper });

    // Fill all fields but don't accept terms
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    // Submit form without accepting terms
    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Wait for validation
    await new Promise((resolve) => setTimeout(resolve, 200));

    await waitFor(
      () => {
        expect(
          screen.getByText(
            "Vous devez accepter les conditions d'utilisation et la politique de confidentialité",
          ),
        ).toBeInTheDocument();
      },
      { timeout: 2000 },
    );

    // Check the checkbox
    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    // Error should be cleared
    await waitFor(() => {
      expect(
        screen.queryByText(
          "Vous devez accepter les conditions d'utilisation et la politique de confidentialité",
        ),
      ).not.toBeInTheDocument();
    });
  });

  it('should allow form submission when terms are accepted', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);
    render(<RegisterPage />, { wrapper });

    // Fill all fields
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    // Accept terms
    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    // Submit form
    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    await waitFor(() => {
      expect(mockHandleRegister).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
        confirmPassword: 'password123',
        username: 'testuser',
      });
    });
  });

  it('should display verification notice after successful registration', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);

    // Start with success: false, then change to true to simulate registration success
    const { rerender } = render(<RegisterPage />, { wrapper });

    // Fill all fields
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    // Accept terms
    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    // Submit form
    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Simulate successful registration by updating the mock
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      success: true,
    });
    rerender(<RegisterPage />);

    // Wait for verification notice to appear
    await waitFor(() => {
      expect(screen.getByText('Inscription réussie !')).toBeInTheDocument();
      expect(
        screen.getByText(
          /Un email de vérification a été envoyé à test@example.com/,
        ),
      ).toBeInTheDocument();
      expect(
        screen.getByText(/Veuillez vérifier votre boîte mail/),
      ).toBeInTheDocument();
    });
  });

  it('should display resend verification email button', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);

    const { rerender } = render(<RegisterPage />, { wrapper });

    // Fill all fields and submit
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Simulate successful registration
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      success: true,
    });
    rerender(<RegisterPage />);

    // Wait for verification notice
    await waitFor(() => {
      expect(screen.getByText('Inscription réussie !')).toBeInTheDocument();
    });

    // Check for resend button
    const resendButton = screen.getByText("Renvoyer l'email de vérification");
    expect(resendButton).toBeInTheDocument();
  });

  it('should call resendVerificationEmail when resend button is clicked', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);
    vi.mocked(resendVerificationEmail).mockResolvedValue(undefined);

    const { rerender } = render(<RegisterPage />, { wrapper });

    // Fill all fields and submit
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Simulate successful registration
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      success: true,
    });
    rerender(<RegisterPage />);

    // Wait for verification notice
    await waitFor(() => {
      expect(screen.getByText('Inscription réussie !')).toBeInTheDocument();
    });

    // Click resend button
    const resendButton = screen.getByText("Renvoyer l'email de vérification");
    await act(async () => {
      await user.click(resendButton);
    });

    await waitFor(() => {
      expect(resendVerificationEmail).toHaveBeenCalledWith('test@example.com');
    });
  });

  it('should show success message after resending verification email', async () => {
    const user = userEvent.setup();
    mockHandleRegister.mockResolvedValue(undefined);
    vi.mocked(resendVerificationEmail).mockResolvedValue(undefined);

    const { rerender } = render(<RegisterPage />, { wrapper });

    // Fill all fields and submit
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'testuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Simulate successful registration
    vi.mocked(useRegister).mockReturnValue({
      ...mockUseRegister,
      success: true,
    });
    rerender(<RegisterPage />);

    // Wait for verification notice
    await waitFor(() => {
      expect(screen.getByText('Inscription réussie !')).toBeInTheDocument();
    });

    // Click resend button
    const resendButton = screen.getByText("Renvoyer l'email de vérification");
    await act(async () => {
      await user.click(resendButton);
    });

    // Wait for success message
    await waitFor(() => {
      expect(
        screen.getByText('Email de vérification renvoyé avec succès !'),
      ).toBeInTheDocument();
    });
  });

  it('should display checking status when username is being verified', async () => {
    const user = userEvent.setup();
    vi.mocked(useUsernameAvailability).mockReturnValue({
      available: null,
      checking: true,
    });

    render(<RegisterPage />, { wrapper });

    // Type username to trigger availability check
    const usernameInput = screen.getByLabelText("Nom d'utilisateur");
    await act(async () => {
      await user.type(usernameInput, 'testuser');
    });

    // Status should be displayed when username is >= 3 chars
    await waitFor(() => {
      expect(screen.getByText('Vérification en cours...')).toBeInTheDocument();
    });
  });

  it('should display available status when username is available', async () => {
    const user = userEvent.setup();
    vi.mocked(useUsernameAvailability).mockReturnValue({
      available: true,
      checking: false,
    });

    render(<RegisterPage />, { wrapper });

    // Type username to trigger availability check
    const usernameInput = screen.getByLabelText("Nom d'utilisateur");
    await act(async () => {
      await user.type(usernameInput, 'availableuser');
    });

    // Status should be displayed when username is >= 3 chars
    await waitFor(() => {
      expect(
        screen.getByText("✓ Ce nom d'utilisateur est disponible"),
      ).toBeInTheDocument();
    });
  });

  it('should display unavailable status when username is taken', async () => {
    const user = userEvent.setup();
    vi.mocked(useUsernameAvailability).mockReturnValue({
      available: false,
      checking: false,
    });

    render(<RegisterPage />, { wrapper });

    // Type username to trigger availability check
    const usernameInput = screen.getByLabelText("Nom d'utilisateur");
    await act(async () => {
      await user.type(usernameInput, 'takenuser');
    });

    // Status should be displayed when username is >= 3 chars
    await waitFor(() => {
      expect(
        screen.getByText("✗ Ce nom d'utilisateur est déjà pris"),
      ).toBeInTheDocument();
    });
  });

  it('should prevent form submission when username is not available', async () => {
    const user = userEvent.setup();
    vi.mocked(useUsernameAvailability).mockReturnValue({
      available: false,
      checking: false,
    });

    render(<RegisterPage />, { wrapper });

    // Fill all fields
    await act(async () => {
      await user.type(screen.getByLabelText("Nom d'utilisateur"), 'takenuser');
      await user.type(screen.getByLabelText('Email'), 'test@example.com');
      await user.type(screen.getByLabelText('Mot de passe'), 'password123');
      await user.type(
        screen.getByLabelText('Confirmer le mot de passe'),
        'password123',
      );
    });

    // Accept terms
    const checkbox = screen.getByRole('checkbox', { name: /j'accepte les/i });
    await act(async () => {
      await user.click(checkbox);
    });

    // Submit form
    const submitButton = screen.getByRole('button', { name: "S'inscrire" });
    await act(async () => {
      await user.click(submitButton);
    });

    // Wait for validation
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Should show error about username being taken
    await waitFor(
      () => {
        expect(
          screen.getByText("Ce nom d'utilisateur est déjà pris"),
        ).toBeInTheDocument();
      },
      { timeout: 2000 },
    );

    // handleRegister should not be called
    expect(mockHandleRegister).not.toHaveBeenCalled();
  });
});
