// src/pages/admin/components/AuthView.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { AuthView } from './AuthView';
import * as apiClient from '../../../services/apiClient';
import { notifySuccess, notifyError } from '../../../services/notificationService';
import { createMockUserProfile } from '../../../tests/utils/mockFactories';

const mockedApiClient = vi.mocked(apiClient, true);

const mockOnClose = vi.fn();
const mockOnLoginSuccess = vi.fn();

vi.mock('../../../components/PasswordInput', () => ({
  // Mock the moved component
  PasswordInput: (props: any) => <input {...props} data-testid="password-input" />,
}));

const defaultProps = {
  onClose: mockOnClose,
  onLoginSuccess: mockOnLoginSuccess,
};

const setupSuccessMocks = () => {
  const mockAuthResponse = {
    userprofile: createMockUserProfile({ user: { user_id: '123', email: 'test@example.com' } }),
    token: 'mock-token',
  };
  (mockedApiClient.loginUser as Mock).mockResolvedValue(
    new Response(JSON.stringify(mockAuthResponse)),
  );
  (mockedApiClient.registerUser as Mock).mockResolvedValue(
    new Response(JSON.stringify(mockAuthResponse)),
  );
  (mockedApiClient.requestPasswordReset as Mock).mockResolvedValue(
    new Response(JSON.stringify({ message: 'Password reset email sent.' })),
  );
};

describe('AuthView', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    setupSuccessMocks();
  });

  describe('Initial Render and Login', () => {
    it('should render the Sign In form by default', () => {
      render(<AuthView {...defaultProps} />);
      expect(screen.getByRole('heading', { name: /sign in/i })).toBeInTheDocument();
      expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
      expect(screen.getByLabelText(/^password$/i)).toBeInTheDocument();
      expect(screen.getByRole('button', { name: /sign in$/i })).toBeInTheDocument();
    });

    it('should allow typing in email and password fields', () => {
      render(<AuthView {...defaultProps} />);
      const emailInput = screen.getByLabelText(/email address/i);
      const passwordInput = screen.getByLabelText(/^password$/i);

      fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
      fireEvent.change(passwordInput, { target: { value: 'password123' } });

      expect(emailInput).toHaveValue('test@example.com');
      expect(passwordInput).toHaveValue('password123');
    });

    it('should call loginUser and onLoginSuccess on successful login', async () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'test@example.com' },
      });
      fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: 'password123' } });
      fireEvent.click(screen.getByRole('checkbox', { name: /remember me/i }));
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        expect(mockedApiClient.loginUser).toHaveBeenCalledWith(
          'test@example.com',
          'password123',
          true,
          expect.any(AbortSignal),
        );
        expect(mockOnLoginSuccess).toHaveBeenCalledWith(
          expect.objectContaining({
            user: expect.objectContaining({ user_id: '123', email: 'test@example.com' }),
          }),
          'mock-token',
          true,
        );
        expect(mockOnClose).toHaveBeenCalled();
      });
    });

    it('should display an error on failed login', async () => {
      (mockedApiClient.loginUser as Mock).mockRejectedValueOnce(new Error('Invalid credentials'));
      render(<AuthView {...defaultProps} />);
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('Invalid credentials');
      });
      expect(mockOnLoginSuccess).not.toHaveBeenCalled();
    });

    it('should display an error on non-OK login response', async () => {
      (mockedApiClient.loginUser as Mock).mockResolvedValueOnce(
        new Response(JSON.stringify({ message: 'Unauthorized' }), { status: 401 }),
      );
      render(<AuthView {...defaultProps} />);
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        // useApi hook should parse the error message from the JSON body
        expect(notifyError).toHaveBeenCalledWith('Unauthorized');
      });
      expect(mockOnLoginSuccess).not.toHaveBeenCalled();
    });
  });

  describe('Registration', () => {
    it('should switch to the registration form', () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));

      expect(screen.getByRole('heading', { name: /create an account/i })).toBeInTheDocument();
      expect(screen.getByLabelText(/full name/i)).toBeInTheDocument();
      expect(screen.getByRole('button', { name: /^register$/i })).toBeInTheDocument();
    });

    it('should call registerUser on successful registration', async () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));

      fireEvent.change(screen.getByLabelText(/full name/i), { target: { value: 'Test User' } });
      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'new@example.com' },
      });
      fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: 'newpassword' } });
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        expect(mockedApiClient.registerUser).toHaveBeenCalledWith(
          'new@example.com',
          'newpassword',
          'Test User',
          '',
          expect.any(AbortSignal),
        );
        expect(mockOnLoginSuccess).toHaveBeenCalledWith(
          expect.objectContaining({ user: expect.objectContaining({ user_id: '123' }) }),
          'mock-token',
          false, // rememberMe is false for registration
        );
        expect(mockOnClose).toHaveBeenCalled();
      });
    });

    it('should allow registration without providing a full name', async () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));

      // Do not fill in the full name, which is marked as optional
      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'noname@example.com' },
      });
      fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: 'password' } });
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        // Verify that registerUser was called with an empty string for the full name
        expect(mockedApiClient.registerUser).toHaveBeenCalledWith(
          'noname@example.com',
          'password',
          '',
          '',
          expect.any(AbortSignal),
        );
        expect(mockOnLoginSuccess).toHaveBeenCalled();
      });
    });

    it('should display an error on failed registration', async () => {
      (mockedApiClient.registerUser as Mock).mockRejectedValueOnce(
        new Error('Email already exists'),
      );
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('Email already exists');
      });
    });

    it('should display an error on non-OK registration response', async () => {
      (mockedApiClient.registerUser as Mock).mockResolvedValueOnce(
        new Response(JSON.stringify({ message: 'User exists' }), { status: 409 }),
      );
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('User exists');
      });
    });
  });

  describe('Forgot Password', () => {
    it('should switch to the reset password form', () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));

      expect(screen.getByRole('heading', { name: /reset password/i })).toBeInTheDocument();
      expect(screen.getByRole('button', { name: /send reset link/i })).toBeInTheDocument();
    });

    it('should call requestPasswordReset and show success message', async () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));

      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'forgot@example.com' },
      });
      fireEvent.submit(screen.getByTestId('reset-password-form'));

      await waitFor(() => {
        expect(mockedApiClient.requestPasswordReset).toHaveBeenCalledWith(
          'forgot@example.com',
          expect.any(AbortSignal),
        );
        expect(notifySuccess).toHaveBeenCalledWith('Password reset email sent.');
      });
    });

    it('should display an error on failed password reset request', async () => {
      (mockedApiClient.requestPasswordReset as Mock).mockRejectedValueOnce(
        new Error('User not found'),
      );
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));
      fireEvent.submit(screen.getByTestId('reset-password-form'));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('User not found');
      });
    });

    it('should display an error on non-OK password reset response', async () => {
      (mockedApiClient.requestPasswordReset as Mock).mockResolvedValueOnce(
        new Response(JSON.stringify({ message: 'Rate limit exceeded' }), { status: 429 }),
      );
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));
      fireEvent.submit(screen.getByTestId('reset-password-form'));

      await waitFor(() => {
        expect(notifyError).toHaveBeenCalledWith('Rate limit exceeded');
      });
    });

    it('should switch back to sign in from forgot password', () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));
      fireEvent.click(screen.getByRole('button', { name: /back to sign in/i }));

      expect(screen.getByRole('heading', { name: /sign in/i })).toBeInTheDocument();
    });
  });

  describe('OAuth', () => {
    const originalLocation = window.location;

    beforeEach(() => {
      Object.defineProperty(window, 'location', {
        writable: true,
        value: { ...originalLocation, href: '' },
      });
    });

    afterEach(() => {
      Object.defineProperty(window, 'location', {
        writable: true,
        value: originalLocation,
      });
    });

    it('should set window.location.href for Google OAuth', () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /sign in with google/i }));
      expect(window.location.href).toBe('/api/auth/google');
    });

    it('should set window.location.href for GitHub OAuth', () => {
      render(<AuthView {...defaultProps} />);
      fireEvent.click(screen.getByRole('button', { name: /sign in with github/i }));
      expect(window.location.href).toBe('/api/auth/github');
    });
  });

  describe('UI Logic and Loading States', () => {
    it('should toggle "Remember me" checkbox', () => {
      render(<AuthView {...defaultProps} />);
      const rememberMeCheckbox = screen.getByRole('checkbox', { name: /remember me/i });

      expect(rememberMeCheckbox).not.toBeChecked();

      fireEvent.click(rememberMeCheckbox);
      expect(rememberMeCheckbox).toBeChecked();

      fireEvent.click(rememberMeCheckbox);
      expect(rememberMeCheckbox).not.toBeChecked();
    });

    it('should show loading state during login submission', async () => {
      // Mock a promise that doesn't resolve immediately
      (mockedApiClient.loginUser as Mock).mockReturnValue(new Promise(() => {}));
      render(<AuthView {...defaultProps} />);

      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'test@example.com' },
      });
      fireEvent.change(screen.getByLabelText(/^password$/i), { target: { value: 'password' } });
      fireEvent.submit(screen.getByTestId('auth-form'));

      await waitFor(() => {
        // Find the submit button. Since the text is replaced by a spinner, we find by type.
        const submitButton = screen.getByTestId('auth-form').querySelector('button[type="submit"]');
        expect(submitButton).toBeInTheDocument();
        expect(submitButton).toBeDisabled();
        // Verify 'Sign In' text is gone from the button
        // Note: We use queryByRole because 'Sign In' still exists in the header (h2).
        expect(screen.queryByRole('button', { name: 'Sign In' })).not.toBeInTheDocument();

        // Also check OAuth buttons are disabled
        expect(screen.getByRole('button', { name: /sign in with google/i })).toBeDisabled();
        expect(screen.getByRole('button', { name: /sign in with github/i })).toBeDisabled();
      });
    });

    it('should show loading state during password reset submission', async () => {
      (mockedApiClient.requestPasswordReset as Mock).mockReturnValue(new Promise(() => {}));
      render(<AuthView {...defaultProps} />);

      fireEvent.click(screen.getByRole('button', { name: /forgot password\?/i }));

      fireEvent.change(screen.getByLabelText(/email address/i), {
        target: { value: 'reset@example.com' },
      });
      fireEvent.submit(screen.getByTestId('reset-password-form'));

      const submitButton = screen
        .getByTestId('reset-password-form')
        .querySelector('button[type="submit"]');
      expect(submitButton).toBeInTheDocument();
      expect(submitButton).toBeDisabled();
      expect(screen.queryByText('Send Reset Link')).not.toBeInTheDocument();
    });
  });

  it('should show loading state during registration submission', async () => {
    // Mock a promise that doesn't resolve immediately
    (mockedApiClient.registerUser as Mock).mockReturnValue(new Promise(() => {}));
    render(<AuthView {...defaultProps} />);

    // Switch to registration view
    fireEvent.click(screen.getByRole('button', { name: /don't have an account\? register/i }));

    fireEvent.change(screen.getByLabelText(/email address/i), {
      target: { value: 'test@example.com' },
    });
    fireEvent.change(screen.getByTestId('password-input'), { target: { value: 'password' } });
    fireEvent.submit(screen.getByTestId('auth-form'));

    await waitFor(() => {
      const submitButton = screen.getByTestId('auth-form').querySelector('button[type="submit"]');
      expect(submitButton).toBeInTheDocument();
      expect(submitButton).toBeDisabled();
      // Verify the text 'Register' is gone from any button
      expect(screen.queryByRole('button', { name: 'Register' })).not.toBeInTheDocument();
    });
  });
});
