/**
 * VerifyEmail Page Tests
 *
 * Tests for the email verification page that handles both
 * automatic token verification and manual code entry.
 */

import React from 'react';
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, type MockedFunction } from 'vitest';
import { MemoryRouter, Routes, Route } from 'react-router';
import VerifyEmail from '../../pages/VerifyEmail';
import { useAuth } from '../../contexts/AuthContext';
import { authAPI } from '../../services/api';
import { mockUser, mockUnverifiedUser } from '../../mocks/handlers';

// Mock dependencies
vi.mock('../../contexts/AuthContext');
vi.mock('../../services/api', () => ({
  authAPI: {
    verifyEmail: vi.fn(),
    resendVerification: vi.fn(),
  },
}));

const mockNavigate = vi.fn();
vi.mock('react-router', async (importOriginal) => {
  const actual = await importOriginal<typeof import('react-router')>();
  return {
    ...actual,
    useNavigate: () => mockNavigate,
  };
});

const mockedUseAuth = useAuth as MockedFunction<typeof useAuth>;
const mockedVerifyEmail = authAPI.verifyEmail as MockedFunction<typeof authAPI.verifyEmail>;
const mockedResendVerification = authAPI.resendVerification as MockedFunction<typeof authAPI.resendVerification>;

// Helper to render VerifyEmail with route params
const renderVerifyEmail = (searchParams: string = '', authOverrides: Partial<ReturnType<typeof useAuth>> = {}) => {
  mockedUseAuth.mockReturnValue({
    user: mockUnverifiedUser,
    loading: false,
    showAuthModal: false,
    authModalMode: 'login',
    login: vi.fn(),
    register: vi.fn(),
    googleLogin: vi.fn(),
    logout: vi.fn(),
    checkAuth: vi.fn(),
    updateUser: vi.fn(),
    openAuthModal: vi.fn(),
    closeAuthModal: vi.fn(),
    ...authOverrides,
  });

  return render(
    <MemoryRouter initialEntries={[`/verify-email${searchParams}`]}>
      <Routes>
        <Route path="/verify-email" element={<VerifyEmail />} />
      </Routes>
    </MemoryRouter>
  );
};

describe('VerifyEmail', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers({ shouldAdvanceTime: true });
    mockedVerifyEmail.mockResolvedValue({ data: { success: true } });
    mockedResendVerification.mockResolvedValue({ data: { success: true } });
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  describe('Initial Loading State', () => {
    it('shows loading state while auth is initializing', async () => {
      renderVerifyEmail('', { loading: true });

      expect(screen.getByRole('status')).toBeInTheDocument();
      // There are multiple "Loading..." elements - the spinner's visually-hidden text and the heading
      expect(screen.getAllByText('Loading...').length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('Authentication Check', () => {
    it('redirects unauthenticated users to login', async () => {
      renderVerifyEmail('', { user: null });

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('/?login=true&redirect='),
          { replace: true }
        );
      });
    });

    it('redirects unauthenticated users with token to login with return URL', async () => {
      renderVerifyEmail('?token=test-token-123', { user: null });

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('login=true'),
          { replace: true }
        );
        expect(mockNavigate).toHaveBeenCalledWith(
          expect.stringContaining('verify-email'),
          { replace: true }
        );
      });
    });
  });

  describe('Auto-Verification with Token', () => {
    it('auto-verifies when token present in URL', async () => {
      renderVerifyEmail('?token=valid-token-123');

      await waitFor(() => {
        expect(mockedVerifyEmail).toHaveBeenCalledWith('valid-token-123');
      });
    });

    it('shows success state after successful verification', async () => {
      const mockCheckAuth = vi.fn();
      renderVerifyEmail('?token=valid-token', { checkAuth: mockCheckAuth });

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

      expect(mockCheckAuth).toHaveBeenCalled();
    });

    it('shows success state immediately for already verified user', async () => {
      renderVerifyEmail('', { user: mockUser });

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

    it('auto-redirects to home after successful verification', async () => {
      renderVerifyEmail('?token=valid-token');

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

      // Advance timers to trigger auto-redirect (3 seconds)
      await act(async () => {
        vi.advanceTimersByTime(3000);
      });

      await waitFor(() => {
        expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true });
      });
    });
  });

  describe('Manual Code Entry', () => {
    it('shows manual code entry form when no token in URL', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
        expect(screen.getByText('Enter the 6-digit code sent to your email')).toBeInTheDocument();
      });

      // Check for 6 input fields
      const inputs = screen.getAllByRole('textbox');
      expect(inputs).toHaveLength(6);
    });

    it('handles 6-digit input with auto-focus', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const inputs = screen.getAllByRole('textbox');

      // Type first digit using fireEvent
      fireEvent.change(inputs[0], { target: { value: '1' } });
      expect(inputs[0]).toHaveValue('1');

      // Focus should auto-move to next input
      // (Note: actual focus behavior may depend on DOM focus events)
    });

    it('filters non-numeric input', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const inputs = screen.getAllByRole('textbox');

      // Try typing letters
      fireEvent.change(inputs[0], { target: { value: 'a' } });
      expect(inputs[0]).toHaveValue('');

      // Try typing numbers
      fireEvent.change(inputs[0], { target: { value: '5' } });
      expect(inputs[0]).toHaveValue('5');
    });

    it('handles paste of 6-digit code', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const container = document.querySelector('.d-flex.justify-content-center.gap-2');

      // Simulate paste event
      const pasteEvent = new Event('paste', { bubbles: true, cancelable: true }) as any;
      pasteEvent.clipboardData = {
        getData: () => '123456',
      };

      fireEvent(container!, pasteEvent);

      await waitFor(() => {
        expect(mockedVerifyEmail).toHaveBeenCalledWith('123456');
      });
    });

    it('submits manual code on button click', async () => {
      // Make the verification hang to test the button state
      mockedVerifyEmail.mockImplementation(() => new Promise(() => {}));

      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const inputs = screen.getAllByRole('textbox');

      // Fill in 5 digits (not 6 to avoid auto-submit)
      fireEvent.change(inputs[0], { target: { value: '1' } });
      fireEvent.change(inputs[1], { target: { value: '2' } });
      fireEvent.change(inputs[2], { target: { value: '3' } });
      fireEvent.change(inputs[3], { target: { value: '4' } });
      fireEvent.change(inputs[4], { target: { value: '5' } });

      // Button should be disabled with only 5 digits
      const verifyButton = screen.getByRole('button', { name: /verify email/i });
      expect(verifyButton).toBeDisabled();

      // Now fill in the 6th digit - this will auto-submit
      fireEvent.change(inputs[5], { target: { value: '6' } });

      // The component auto-submits when 6 digits are entered
      await waitFor(() => {
        expect(mockedVerifyEmail).toHaveBeenCalledWith('123456');
      });
    });

    it('disables verify button when code incomplete', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

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

    it('backspace moves focus to previous input', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const inputs = screen.getAllByRole('textbox');

      // Fill first input and move to second
      fireEvent.change(inputs[0], { target: { value: '1' } });
      fireEvent.change(inputs[1], { target: { value: '2' } });

      // Clear second input and press backspace
      fireEvent.change(inputs[1], { target: { value: '' } });
      fireEvent.keyDown(inputs[1], { key: 'Backspace' });

      // The component handles this by focusing previous input
    });
  });

  describe('Error Handling', () => {
    it('displays EXPIRED error message', async () => {
      mockedVerifyEmail.mockRejectedValue({
        response: { data: { code: 'VERIFICATION_EXPIRED' } },
      });

      renderVerifyEmail('?token=expired-token');

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

    it('displays INVALID error message', async () => {
      mockedVerifyEmail.mockRejectedValue({
        response: { data: { code: 'VERIFICATION_INVALID' } },
      });

      renderVerifyEmail('?token=invalid-token');

      await waitFor(() => {
        expect(screen.getByText(/code didn't match/i)).toBeInTheDocument();
      });
    });

    it('displays TOO_MANY_ATTEMPTS error message', async () => {
      mockedVerifyEmail.mockRejectedValue({
        response: { data: { code: 'TOO_MANY_ATTEMPTS' } },
      });

      renderVerifyEmail('?token=blocked-token');

      await waitFor(() => {
        expect(screen.getByText(/too many attempts/i)).toBeInTheDocument();
      });
    });

    it('displays ALREADY_VERIFIED error message', async () => {
      mockedVerifyEmail.mockRejectedValue({
        response: { data: { code: 'ALREADY_VERIFIED' } },
      });

      renderVerifyEmail('?token=already-verified-token');

      await waitFor(() => {
        expect(screen.getByText(/already verified/i)).toBeInTheDocument();
      });
    });

    it('clears input on error', async () => {
      mockedVerifyEmail.mockRejectedValue({
        response: { data: { code: 'VERIFICATION_INVALID' } },
      });

      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const inputs = screen.getAllByRole('textbox');

      // Fill all digits - this will auto-submit due to the component behavior
      fireEvent.change(inputs[0], { target: { value: '1' } });
      fireEvent.change(inputs[1], { target: { value: '2' } });
      fireEvent.change(inputs[2], { target: { value: '3' } });
      fireEvent.change(inputs[3], { target: { value: '4' } });
      fireEvent.change(inputs[4], { target: { value: '5' } });
      fireEvent.change(inputs[5], { target: { value: '6' } });

      // Wait for error message to appear
      await waitFor(() => {
        expect(screen.getByText(/code didn't match/i)).toBeInTheDocument();
      });

      // Inputs should be cleared after error
      await waitFor(() => {
        const updatedInputs = screen.getAllByRole('textbox');
        updatedInputs.forEach((input) => {
          expect(input).toHaveValue('');
        });
      });
    });
  });

  describe('Resend Verification', () => {
    it('shows resend button', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      expect(screen.getByRole('button', { name: /send code/i })).toBeInTheDocument();
    });

    it('starts 60-second cooldown after resend', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/resend in 60s/i)).toBeInTheDocument();
      });

      expect(mockedResendVerification).toHaveBeenCalled();
    });

    it('disables resend during cooldown', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/resend in 60s/i)).toBeInTheDocument();
      });

      const cooldownButton = screen.getByRole('button', { name: /resend in/i });
      expect(cooldownButton).toBeDisabled();
    });

    it('shows success message after resend', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/new code sent/i)).toBeInTheDocument();
      });
    });

    it('counts down timer', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/resend in 60s/i)).toBeInTheDocument();
      });

      // With shouldAdvanceTime: true, the timer will automatically count down
      // Wait for the countdown to show a lower value
      await waitFor(() => {
        // Timer should have counted down from 60s to something less
        const resendText = screen.getByRole('button', { name: /resend in \d+s/i }).textContent;
        expect(resendText).toMatch(/Resend in [0-5][0-9]s/);
      }, { timeout: 3000 });
    });

    it('handles resend error for already verified', async () => {
      mockedResendVerification.mockRejectedValue({
        response: { data: { code: 'ALREADY_VERIFIED' } },
      });

      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/already verified/i)).toBeInTheDocument();
      });
    });

    it('handles rate limit error (429)', async () => {
      mockedResendVerification.mockRejectedValue({
        response: { status: 429 },
      });

      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const resendButton = screen.getByRole('button', { name: /send code/i });
      fireEvent.click(resendButton);

      await waitFor(() => {
        expect(screen.getByText(/please wait before requesting/i)).toBeInTheDocument();
      });
    });
  });

  describe('Navigation', () => {
    it('has return to home link', async () => {
      renderVerifyEmail();

      await waitFor(() => {
        expect(screen.getByText('Enter Verification Code')).toBeInTheDocument();
      });

      const homeLink = screen.getByRole('link', { name: /return to home/i });
      expect(homeLink).toHaveAttribute('href', '/');
    });

    it('has go to home link on success', async () => {
      renderVerifyEmail('?token=valid-token');

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

      const homeLink = screen.getByRole('link', { name: /go to home page/i });
      expect(homeLink).toHaveAttribute('href', '/');
    });
  });
});
