import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
import VerifyOtpPage from '../app/(auth)/verify-otp/page';
import api from '../lib/api';

// Helper to render with Suspense
function renderWithSuspense(component: React.ReactNode) {
  return render(
    <Suspense fallback={<div>Loading...</div>}>
      {component}
    </Suspense>
  );
}

// Mock Next.js router
jest.mock('next/navigation', () => ({
  useRouter: jest.fn(),
  useSearchParams: jest.fn(),
}));

// Mock API
jest.mock('../lib/api', () => ({
  __esModule: true,
  default: {
    verifyOTP: jest.fn(),
    requestOTP: jest.fn(),
    getProfile: jest.fn(),
    getAllBeneficiaries: jest.fn(),
  },
}));

describe('VerifyOtpPage', () => {
  const mockPush = jest.fn();
  const mockReplace = jest.fn();
  const mockBack = jest.fn();
  const mockGet = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
    (useRouter as jest.Mock).mockReturnValue({
      push: mockPush,
      replace: mockReplace,
      back: mockBack,
    });
    (useSearchParams as jest.Mock).mockReturnValue({
      get: mockGet,
    });
    mockGet.mockImplementation((key: string) => {
      if (key === 'email') return 'test@example.com';
      return null;
    });
  });

  it('renders OTP verification form correctly', () => {
    render(<VerifyOtpPage />);

    expect(screen.getByText('Check your email')).toBeInTheDocument();
    expect(screen.getByText('test@example.com')).toBeInTheDocument();
    expect(screen.getByText('Enter verification code')).toBeInTheDocument();
  });

  it('displays all 6 OTP input fields', () => {
    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    expect(inputs).toHaveLength(6);
    inputs.forEach(input => {
      expect(input).toHaveAttribute('maxLength', '1');
      expect(input).toHaveAttribute('inputMode', 'numeric');
    });
  });

  it('auto-focuses first input on mount', () => {
    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    expect(document.activeElement).toBe(inputs[0]);
  });

  it('only allows numeric input', () => {
    render(<VerifyOtpPage />);

    const input = screen.getAllByRole('textbox')[0];
    fireEvent.change(input, { target: { value: 'a' } });

    expect(input).toHaveValue('');
  });

  it('accepts numeric input and moves to next field', () => {
    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    fireEvent.change(inputs[0], { target: { value: '1' } });

    expect(inputs[0]).toHaveValue('1');
    expect(document.activeElement).toBe(inputs[1]);
  });

  it('handles backspace navigation', () => {
    render(<VerifyOtpPage />);

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

    // Enter digits
    fireEvent.change(inputs[0], { target: { value: '1' } });
    fireEvent.change(inputs[1], { target: { value: '2' } });

    // Clear the second field first
    fireEvent.change(inputs[1], { target: { value: '' } });

    // Press backspace on empty field
    fireEvent.keyDown(inputs[1], { key: 'Backspace' });

    expect(document.activeElement).toBe(inputs[0]);
  });

  it('handles paste of 6-digit code', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'mock-token',
        user: { id: '1', email: 'test@example.com', first_name: 'Test', last_name: 'User' },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 1, firstName: 'Test', lastName: 'User' },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [{ id: 1, name: 'Beneficiary' }],
    });

    render(<VerifyOtpPage />);

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

    // Simulate paste event with clipboardData
    fireEvent.paste(inputs[0], {
      clipboardData: {
        getData: () => '123456',
      },
    });

    await waitFor(() => {
      expect(api.verifyOTP).toHaveBeenCalledWith('test@example.com', '123456');
    });
  });

  it('filters non-numeric characters from pasted data', () => {
    render(<VerifyOtpPage />);

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

    // Simulate paste event with clipboardData
    fireEvent.paste(inputs[0], {
      clipboardData: {
        getData: () => 'a1b2c3d4e5f6',
      },
    });

    expect(inputs[0]).toHaveValue('1');
    expect(inputs[1]).toHaveValue('2');
    expect(inputs[2]).toHaveValue('3');
    expect(inputs[3]).toHaveValue('4');
    expect(inputs[4]).toHaveValue('5');
    expect(inputs[5]).toHaveValue('6');
  });

  it('auto-submits when all 6 digits are entered', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'mock-token',
        user: { id: '1', email: 'test@example.com', first_name: 'Test' },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 1, firstName: 'Test' },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [{ id: 1 }],
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(api.verifyOTP).toHaveBeenCalledWith('test@example.com', '123456');
    });
  });

  it('shows error for incomplete code', async () => {
    render(<VerifyOtpPage />);

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

    // Enter only 3 digits
    fireEvent.change(inputs[0], { target: { value: '1' } });
    fireEvent.change(inputs[1], { target: { value: '2' } });
    fireEvent.change(inputs[2], { target: { value: '3' } });

    // Button should be disabled with incomplete code, so we can't test the error this way
    // Instead, let's verify that button is disabled
    const verifyButton = screen.getByRole('button', { name: /verify code/i });
    expect(verifyButton).toBeDisabled();
  });

  it('navigates to enter-name if user has no firstName', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'mock-token',
        user: { id: '1', email: 'test@example.com', first_name: null },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 1, firstName: null },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [],
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/enter-name');
    });
  });

  it('navigates to add-loved-one if user has no beneficiaries', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'mock-token',
        user: { id: '1', email: 'test@example.com', first_name: 'Test' },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 1, firstName: 'Test' },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [],
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/add-loved-one');
    });
  });

  it('navigates to dashboard if user has profile and beneficiaries', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'mock-token',
        user: { id: '1', email: 'test@example.com', first_name: 'Test', last_name: 'User' },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 1, firstName: 'Test', lastName: 'User' },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [{ id: 1, name: 'Beneficiary' }],
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(mockReplace).toHaveBeenCalledWith('/dashboard');
    });
  });

  it('shows error message for invalid OTP', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: false,
      error: { message: 'Invalid verification code' },
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(screen.getByText('Invalid verification code')).toBeInTheDocument();
    });
  });

  it('clears input fields after failed verification', async () => {
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: false,
      error: { message: 'Invalid code' },
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      inputs.forEach(input => {
        expect(input).toHaveValue('');
      });
    });
  });

  it('shows loading state during verification', async () => {
    (api.verifyOTP as jest.Mock).mockImplementation(() => new Promise(() => {})); // Never resolves

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

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

  it('disables inputs during verification', async () => {
    (api.verifyOTP as jest.Mock).mockImplementation(() => new Promise(() => {}));

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      inputs.forEach(input => {
        expect(input).toBeDisabled();
      });
    });
  });

  it('shows resend countdown initially', () => {
    render(<VerifyOtpPage />);

    expect(screen.getByText(/Resend code in \d+s/)).toBeInTheDocument();
  });

  it('enables resend button after countdown', async () => {
    jest.useFakeTimers();

    render(<VerifyOtpPage />);

    // Fast-forward 60 seconds with act wrapper
    for (let i = 0; i < 60; i++) {
      act(() => {
        jest.advanceTimersByTime(1000);
      });
    }

    await waitFor(() => {
      expect(screen.getByText('Resend code')).toBeInTheDocument();
    });

    jest.useRealTimers();
  });

  it('resends OTP code successfully', async () => {
    jest.useFakeTimers();
    (api.requestOTP as jest.Mock).mockResolvedValue({ ok: true });

    render(<VerifyOtpPage />);

    // Fast-forward to enable resend
    for (let i = 0; i < 60; i++) {
      act(() => {
        jest.advanceTimersByTime(1000);
      });
    }

    await waitFor(() => {
      const resendButton = screen.getByText('Resend code');
      fireEvent.click(resendButton);
    });

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

    jest.useRealTimers();
  });

  it('shows error on failed resend', async () => {
    jest.useFakeTimers();
    (api.requestOTP as jest.Mock).mockResolvedValue({
      ok: false,
      error: { message: 'Failed to send OTP' },
    });

    render(<VerifyOtpPage />);

    // Fast-forward to enable resend
    for (let i = 0; i < 60; i++) {
      act(() => {
        jest.advanceTimersByTime(1000);
      });
    }

    await waitFor(() => {
      const resendButton = screen.getByText('Resend code');
      fireEvent.click(resendButton);
    });

    await waitFor(() => {
      expect(screen.getByText('Failed to resend code. Please try again.')).toBeInTheDocument();
    });

    jest.useRealTimers();
  });

  it('navigates back to login on back button click', () => {
    render(<VerifyOtpPage />);

    const backButton = screen.getByText('Back to login').closest('button');
    fireEvent.click(backButton!);

    expect(mockBack).toHaveBeenCalled();
  });

  it('handles network errors gracefully', async () => {
    (api.verifyOTP as jest.Mock).mockRejectedValue(new Error('Network error'));

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(screen.getByText('Network error. Please try again.')).toBeInTheDocument();
    });
  });

  it('saves auth token to localStorage on successful verification', async () => {
    const mockSetItem = jest.spyOn(Storage.prototype, 'setItem');
    (api.verifyOTP as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        token: 'test-token',
        user: { id: '123', email: 'test@example.com', first_name: 'Test' },
      },
    });
    (api.getProfile as jest.Mock).mockResolvedValue({
      ok: true,
      data: {
        user: { id: 123, firstName: 'Test' },
      },
    });
    (api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
      ok: true,
      data: [{ id: 1 }],
    });

    render(<VerifyOtpPage />);

    const inputs = screen.getAllByRole('textbox');
    '123456'.split('').forEach((digit, index) => {
      fireEvent.change(inputs[index], { target: { value: digit } });
    });

    await waitFor(() => {
      expect(mockSetItem).toHaveBeenCalledWith('accessToken', 'test-token');
      expect(mockSetItem).toHaveBeenCalledWith('userId', expect.any(String));
    });

    mockSetItem.mockRestore();
  });
});
