/**
 * AuthModal Component Tests
 *
 * Tests for the AuthModal component including login, signup,
 * form validation, and modal behavior.
 */

import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import AuthModal from '../../components/AuthModal';

// Mock the auth context
const mockLogin = jest.fn();
const mockRegister = jest.fn();

jest.mock('../../contexts/AuthContext', () => ({
  ...jest.requireActual('../../contexts/AuthContext'),
  useAuth: () => ({
    login: mockLogin,
    register: mockRegister,
    user: null,
    loading: false,
  }),
}));

// Mock child components
jest.mock('../../components/PasswordStrengthMeter', () => {
  return function MockPasswordStrengthMeter({ password }: { password: string }) {
    return <div data-testid="password-strength-meter">Strength: {password.length > 8 ? 'Strong' : 'Weak'}</div>;
  };
});

jest.mock('../../components/PasswordInput', () => {
  return function MockPasswordInput({
    id,
    label,
    value,
    onChange,
    required
  }: {
    id: string;
    label: string;
    value: string;
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    required?: boolean;
  }) {
    return (
      <div className="mb-3">
        <label htmlFor={id} className="form-label">{label}</label>
        <input
          id={id}
          type="password"
          className="form-control"
          value={value}
          onChange={onChange}
          required={required}
          data-testid="password-input"
        />
      </div>
    );
  };
});

jest.mock('../../components/ForgotPasswordModal', () => {
  return function MockForgotPasswordModal({
    show,
    onHide,
    onBackToLogin
  }: {
    show: boolean;
    onHide: () => void;
    onBackToLogin: () => void;
  }) {
    if (!show) return null;
    return (
      <div data-testid="forgot-password-modal">
        <button onClick={onBackToLogin} data-testid="back-to-login">Back to Login</button>
        <button onClick={onHide}>Close</button>
      </div>
    );
  };
});

jest.mock('../../components/VerificationCodeModal', () => {
  return function MockVerificationCodeModal({
    show,
    onHide,
    email,
    onVerified
  }: {
    show: boolean;
    onHide: () => void;
    email: string;
    onVerified: () => void;
  }) {
    if (!show) return null;
    return (
      <div data-testid="verification-modal">
        <p>Verify email: {email}</p>
        <button onClick={onVerified} data-testid="verify-button">Verify</button>
        <button onClick={onHide}>Close</button>
      </div>
    );
  };
});

describe('AuthModal', () => {
  const defaultProps = {
    show: true,
    onHide: jest.fn(),
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  // Helper to get email input (it's a textbox with type email)
  const getEmailInput = () => screen.getByRole('textbox', { hidden: false });

  // Helper to get inputs by their preceding label text
  const getInputByLabelText = (container: HTMLElement, labelText: string) => {
    const label = Array.from(container.querySelectorAll('label')).find(
      l => l.textContent === labelText
    );
    if (!label) throw new Error(`Label "${labelText}" not found`);
    // Get the next sibling input or the input inside the same parent
    const parent = label.parentElement;
    return parent?.querySelector('input') as HTMLInputElement;
  };

  describe('Rendering', () => {
    it('should render login form by default', () => {
      const { container } = render(<AuthModal {...defaultProps} />);

      expect(screen.getByText('Welcome to CommunityRentals.App')).toBeInTheDocument();
      expect(getInputByLabelText(container, 'Email')).toBeInTheDocument();
      expect(screen.getByTestId('password-input')).toBeInTheDocument();
      expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
    });

    it('should render signup form when initialMode is signup', () => {
      const { container } = render(<AuthModal {...defaultProps} initialMode="signup" />);

      expect(getInputByLabelText(container, 'First Name')).toBeInTheDocument();
      expect(getInputByLabelText(container, 'Last Name')).toBeInTheDocument();
      expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument();
      expect(screen.getByTestId('password-strength-meter')).toBeInTheDocument();
    });

    it('should not render when show is false', () => {
      render(<AuthModal {...defaultProps} show={false} />);

      expect(screen.queryByText('Welcome to CommunityRentals.App')).not.toBeInTheDocument();
    });

    it('should render Google login button', () => {
      render(<AuthModal {...defaultProps} />);

      expect(screen.getByRole('button', { name: /continue with google/i })).toBeInTheDocument();
    });

    it('should render forgot password link in login mode', () => {
      render(<AuthModal {...defaultProps} />);

      expect(screen.getByText('Forgot password?')).toBeInTheDocument();
    });

    it('should not render forgot password link in signup mode', () => {
      render(<AuthModal {...defaultProps} initialMode="signup" />);

      expect(screen.queryByText('Forgot password?')).not.toBeInTheDocument();
    });
  });

  describe('Mode Switching', () => {
    it('should switch from login to signup mode', async () => {
      const { container } = render(<AuthModal {...defaultProps} />);

      // Initially in login mode
      expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();

      // Click "Sign up" link
      fireEvent.click(screen.getByText('Sign up'));

      // Should now be in signup mode
      expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument();
      expect(getInputByLabelText(container, 'First Name')).toBeInTheDocument();
    });

    it('should switch from signup to login mode', async () => {
      render(<AuthModal {...defaultProps} initialMode="signup" />);

      // Initially in signup mode
      expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument();

      // Click "Log in" link
      fireEvent.click(screen.getByText('Log in'));

      // Should now be in login mode
      expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
      expect(screen.queryByText('First Name')).not.toBeInTheDocument();
    });
  });

  describe('Login Form Submission', () => {
    it('should call login with email and password', async () => {
      mockLogin.mockResolvedValue({});
      const { container } = render(<AuthModal {...defaultProps} />);

      // Fill in the form
      const emailInput = getInputByLabelText(container, 'Email');
      await userEvent.type(emailInput, 'test@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'password123');

      // Submit the form
      fireEvent.click(screen.getByRole('button', { name: 'Log in' }));

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

    it('should call onHide after successful login', async () => {
      mockLogin.mockResolvedValue({});
      const { container } = render(<AuthModal {...defaultProps} />);

      const emailInput = getInputByLabelText(container, 'Email');
      await userEvent.type(emailInput, 'test@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'password123');

      fireEvent.click(screen.getByRole('button', { name: 'Log in' }));

      await waitFor(() => {
        expect(defaultProps.onHide).toHaveBeenCalled();
      });
    });

    it('should display error message on login failure', async () => {
      mockLogin.mockRejectedValue({
        response: { data: { error: 'Invalid credentials' } },
      });

      const { container } = render(<AuthModal {...defaultProps} />);

      const emailInput = getInputByLabelText(container, 'Email');
      await userEvent.type(emailInput, 'test@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'wrongpassword');

      fireEvent.click(screen.getByRole('button', { name: 'Log in' }));

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

    it('should show loading state during login', async () => {
      // Make login take some time
      mockLogin.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));

      const { container } = render(<AuthModal {...defaultProps} />);

      const emailInput = getInputByLabelText(container, 'Email');
      await userEvent.type(emailInput, 'test@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'password123');

      fireEvent.click(screen.getByRole('button', { name: 'Log in' }));

      expect(screen.getByRole('button', { name: 'Loading...' })).toBeInTheDocument();
    });
  });

  describe('Signup Form Submission', () => {
    it('should call register with user data', async () => {
      mockRegister.mockResolvedValue({});
      const { container } = render(<AuthModal {...defaultProps} initialMode="signup" />);

      await userEvent.type(getInputByLabelText(container, 'First Name'), 'John');
      await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe');
      await userEvent.type(getInputByLabelText(container, 'Email'), 'john@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!');

      fireEvent.click(screen.getByRole('button', { name: 'Sign up' }));

      await waitFor(() => {
        expect(mockRegister).toHaveBeenCalledWith({
          email: 'john@example.com',
          password: 'StrongPass123!',
          firstName: 'John',
          lastName: 'Doe',
          username: 'john', // Generated from email
        });
      });
    });

    it('should show verification modal after successful signup', async () => {
      mockRegister.mockResolvedValue({});
      const { container } = render(<AuthModal {...defaultProps} initialMode="signup" />);

      await userEvent.type(getInputByLabelText(container, 'First Name'), 'John');
      await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe');
      await userEvent.type(getInputByLabelText(container, 'Email'), 'john@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!');

      fireEvent.click(screen.getByRole('button', { name: 'Sign up' }));

      await waitFor(() => {
        expect(screen.getByTestId('verification-modal')).toBeInTheDocument();
        expect(screen.getByText('Verify email: john@example.com')).toBeInTheDocument();
      });
    });

    it('should display error message on signup failure', async () => {
      mockRegister.mockRejectedValue({
        response: { data: { error: 'Email already exists' } },
      });

      const { container } = render(<AuthModal {...defaultProps} initialMode="signup" />);

      await userEvent.type(getInputByLabelText(container, 'First Name'), 'John');
      await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe');
      await userEvent.type(getInputByLabelText(container, 'Email'), 'existing@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!');

      fireEvent.click(screen.getByRole('button', { name: 'Sign up' }));

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

  describe('Forgot Password', () => {
    it('should show forgot password modal when link is clicked', async () => {
      render(<AuthModal {...defaultProps} />);

      fireEvent.click(screen.getByText('Forgot password?'));

      expect(screen.getByTestId('forgot-password-modal')).toBeInTheDocument();
    });

    it('should hide forgot password modal and show login when back is clicked', async () => {
      render(<AuthModal {...defaultProps} />);

      // Open forgot password modal
      fireEvent.click(screen.getByText('Forgot password?'));
      expect(screen.getByTestId('forgot-password-modal')).toBeInTheDocument();

      // Click back to login
      fireEvent.click(screen.getByTestId('back-to-login'));

      // Should show login form again
      await waitFor(() => {
        expect(screen.queryByTestId('forgot-password-modal')).not.toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument();
      });
    });
  });

  describe('Modal Close', () => {
    it('should call onHide when close button is clicked', async () => {
      render(<AuthModal {...defaultProps} />);

      // Click close button (btn-close class)
      const closeButton = document.querySelector('.btn-close') as HTMLButtonElement;
      fireEvent.click(closeButton);

      expect(defaultProps.onHide).toHaveBeenCalled();
    });
  });

  describe('Google OAuth', () => {
    it('should redirect to Google OAuth when Google button is clicked', () => {
      // Mock window.location
      const originalLocation = window.location;
      delete (window as any).location;
      window.location = { ...originalLocation, href: '' } as Location;

      render(<AuthModal {...defaultProps} />);

      fireEvent.click(screen.getByRole('button', { name: /continue with google/i }));

      // Check that window.location.href was set to Google OAuth URL
      expect(window.location.href).toContain('accounts.google.com');

      // Restore
      window.location = originalLocation;
    });
  });

  describe('Accessibility', () => {
    it('should have password label associated with input', () => {
      render(<AuthModal {...defaultProps} initialMode="signup" />);

      // Password input has proper htmlFor through the mock
      expect(screen.getByLabelText('Password')).toBeInTheDocument();
    });

    it('should display error in an alert role', async () => {
      mockLogin.mockRejectedValue({
        response: { data: { error: 'Test error' } },
      });

      const { container } = render(<AuthModal {...defaultProps} />);

      const emailInput = getInputByLabelText(container, 'Email');
      await userEvent.type(emailInput, 'test@example.com');
      await userEvent.type(screen.getByTestId('password-input'), 'password');
      fireEvent.click(screen.getByRole('button', { name: 'Log in' }));

      await waitFor(() => {
        expect(screen.getByRole('alert')).toBeInTheDocument();
      });
    });
  });

  describe('Terms and Privacy Links', () => {
    it('should display terms and privacy links', () => {
      render(<AuthModal {...defaultProps} />);

      expect(screen.getByText('Terms of Service')).toHaveAttribute('href', '/terms');
      expect(screen.getByText('Privacy Policy')).toHaveAttribute('href', '/privacy');
    });
  });
});
