import React from 'react';
import { render, screen, waitFor, act, fireEvent } from '@testing-library/react';
import { AuthProvider, useAuth } from '../../contexts/AuthContext';
import { mockUser } from '../../mocks/handlers';

// Mock the API module
jest.mock('../../services/api', () => {
  const mockAuthAPI = {
    login: jest.fn(),
    register: jest.fn(),
    googleLogin: jest.fn(),
    logout: jest.fn(),
    getStatus: jest.fn(),
  };

  const mockFetchCSRFToken = jest.fn().mockResolvedValue('test-csrf-token');
  const mockResetCSRFToken = jest.fn();

  return {
    authAPI: mockAuthAPI,
    fetchCSRFToken: mockFetchCSRFToken,
    resetCSRFToken: mockResetCSRFToken,
  };
});

// Get mocked modules
import { authAPI, fetchCSRFToken, resetCSRFToken } from '../../services/api';

const mockAuthAPI = authAPI as jest.Mocked<typeof authAPI>;
const mockFetchCSRFToken = fetchCSRFToken as jest.MockedFunction<typeof fetchCSRFToken>;
const mockResetCSRFToken = resetCSRFToken as jest.MockedFunction<typeof resetCSRFToken>;

// Test component that uses the auth context
const TestComponent: React.FC = () => {
  const auth = useAuth();

  return (
    <div>
      <div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
      <div data-testid="user">{auth.user ? auth.user.email : 'no-user'}</div>
      <div data-testid="verified">{auth.user?.isVerified ? 'verified' : 'not-verified'}</div>
      <div data-testid="modal-open">{auth.showAuthModal ? 'open' : 'closed'}</div>
      <div data-testid="modal-mode">{auth.authModalMode}</div>
      <button onClick={() => auth.login('test@example.com', 'password123')}>Login</button>
      <button onClick={() => auth.register({ email: 'new@example.com', username: 'newuser', password: 'password123' })}>Register</button>
      <button onClick={() => auth.googleLogin('valid-google-code')}>Google Login</button>
      <button onClick={() => auth.logout()}>Logout</button>
      <button onClick={() => auth.openAuthModal('login')}>Open Login Modal</button>
      <button onClick={() => auth.openAuthModal('signup')}>Open Signup Modal</button>
      <button onClick={() => auth.closeAuthModal()}>Close Modal</button>
      <button onClick={() => auth.checkAuth()}>Check Auth</button>
    </div>
  );
};

// Wrapper component for testing
const renderWithAuth = (ui: React.ReactElement = <TestComponent />) => {
  return render(
    <AuthProvider>
      {ui}
    </AuthProvider>
  );
};

describe('AuthContext', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    // Default: user is authenticated
    mockAuthAPI.getStatus.mockResolvedValue({
      data: { authenticated: true, user: mockUser },
    });
    mockFetchCSRFToken.mockResolvedValue('test-csrf-token');
  });

  describe('useAuth hook', () => {
    it('throws error when used outside AuthProvider', () => {
      const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});

      expect(() => render(<TestComponent />)).toThrow('useAuth must be used within an AuthProvider');

      consoleError.mockRestore();
    });
  });

  describe('Initial State', () => {
    it('starts with loading state', () => {
      renderWithAuth();
      expect(screen.getByTestId('loading')).toHaveTextContent('loading');
    });

    it('checks authentication status on mount', async () => {
      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      expect(mockAuthAPI.getStatus).toHaveBeenCalled();
    });

    it('sets user to null when not authenticated', async () => {
      mockAuthAPI.getStatus.mockResolvedValue({
        data: { authenticated: false, user: null },
      });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user')).toHaveTextContent('no-user');
    });

    it('handles network errors gracefully', async () => {
      mockAuthAPI.getStatus.mockRejectedValue(new Error('Network error'));

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user')).toHaveTextContent('no-user');
    });
  });

  describe('Login', () => {
    it('logs in successfully with valid credentials', async () => {
      mockAuthAPI.getStatus.mockResolvedValue({
        data: { authenticated: false, user: null },
      });

      mockAuthAPI.login.mockResolvedValue({
        data: { user: mockUser },
      });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user')).toHaveTextContent('no-user');

      await act(async () => {
        fireEvent.click(screen.getByText('Login'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      });

      expect(mockAuthAPI.login).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
    });

    it('keeps user as null when login fails', async () => {
      mockAuthAPI.getStatus.mockResolvedValue({
        data: { authenticated: false, user: null },
      });

      mockAuthAPI.login.mockRejectedValue(new Error('Invalid credentials'));

      // Create a test component that captures login errors
      const LoginErrorTestComponent: React.FC = () => {
        const auth = useAuth();
        const [error, setError] = React.useState<string | null>(null);

        const handleLogin = async () => {
          try {
            await auth.login('test@example.com', 'wrongpassword');
          } catch (err: any) {
            setError(err.message);
          }
        };

        return (
          <div>
            <div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
            <div data-testid="user">{auth.user ? auth.user.email : 'no-user'}</div>
            <div data-testid="error">{error || 'no-error'}</div>
            <button onClick={handleLogin}>Login</button>
          </div>
        );
      };

      render(
        <AuthProvider>
          <LoginErrorTestComponent />
        </AuthProvider>
      );

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Login'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('error')).toHaveTextContent('Invalid credentials');
      });

      // User should still be null after failed login
      expect(screen.getByTestId('user')).toHaveTextContent('no-user');
    });
  });

  describe('Registration', () => {
    it('registers a new user successfully', async () => {
      mockAuthAPI.getStatus.mockResolvedValue({
        data: { authenticated: false, user: null },
      });

      mockAuthAPI.register.mockResolvedValue({
        data: { user: { ...mockUser, email: 'new@example.com', isVerified: false } },
      });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Register'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('new@example.com');
      });
    });
  });

  describe('Google Login', () => {
    it('logs in with Google successfully', async () => {
      mockAuthAPI.getStatus.mockResolvedValue({
        data: { authenticated: false, user: null },
      });

      mockAuthAPI.googleLogin.mockResolvedValue({
        data: { user: mockUser },
      });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Google Login'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      });

      expect(mockAuthAPI.googleLogin).toHaveBeenCalledWith('valid-google-code');
    });
  });

  describe('Logout', () => {
    it('logs out successfully', async () => {
      mockAuthAPI.logout.mockResolvedValue({ data: { message: 'Logged out' } });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Logout'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('no-user');
      });

      expect(mockResetCSRFToken).toHaveBeenCalled();
    });

    it('clears user state even if logout API fails', async () => {
      mockAuthAPI.logout.mockRejectedValue(new Error('Server error'));

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Logout'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('no-user');
      });
    });
  });

  describe('Auth Modal', () => {
    it('opens login modal', async () => {
      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('modal-open')).toHaveTextContent('closed');

      await act(async () => {
        fireEvent.click(screen.getByText('Open Login Modal'));
      });

      expect(screen.getByTestId('modal-open')).toHaveTextContent('open');
      expect(screen.getByTestId('modal-mode')).toHaveTextContent('login');
    });

    it('opens signup modal', async () => {
      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Open Signup Modal'));
      });

      expect(screen.getByTestId('modal-open')).toHaveTextContent('open');
      expect(screen.getByTestId('modal-mode')).toHaveTextContent('signup');
    });

    it('closes modal', async () => {
      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      await act(async () => {
        fireEvent.click(screen.getByText('Open Login Modal'));
      });

      expect(screen.getByTestId('modal-open')).toHaveTextContent('open');

      await act(async () => {
        fireEvent.click(screen.getByText('Close Modal'));
      });

      expect(screen.getByTestId('modal-open')).toHaveTextContent('closed');
    });
  });

  describe('updateUser', () => {
    it('updates user state', async () => {
      const TestComponentWithUpdate: React.FC = () => {
        const auth = useAuth();

        return (
          <div>
            <div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
            <div data-testid="user-email">{auth.user?.email || 'no-user'}</div>
            <div data-testid="user-name">{auth.user?.firstName || 'no-name'}</div>
            <button onClick={() => auth.updateUser({
              ...auth.user!,
              firstName: 'Updated',
              lastName: 'Name',
            })}>
              Update User
            </button>
          </div>
        );
      };

      renderWithAuth(<TestComponentWithUpdate />);

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user-name')).toHaveTextContent('Test');

      await act(async () => {
        fireEvent.click(screen.getByText('Update User'));
      });

      expect(screen.getByTestId('user-name')).toHaveTextContent('Updated');
    });
  });

  describe('checkAuth', () => {
    it('refreshes authentication status', async () => {
      let callCount = 0;

      mockAuthAPI.getStatus.mockImplementation(() => {
        callCount++;
        if (callCount === 1) {
          return Promise.resolve({ data: { authenticated: false, user: null } });
        }
        return Promise.resolve({ data: { authenticated: true, user: mockUser } });
      });

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      expect(screen.getByTestId('user')).toHaveTextContent('no-user');

      await act(async () => {
        fireEvent.click(screen.getByText('Check Auth'));
      });

      await waitFor(() => {
        expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
      });
    });
  });

  describe('OAuth Callback Handling', () => {
    it('skips auth check on OAuth callback page', async () => {
      // Mock being on the OAuth callback page
      Object.defineProperty(window, 'location', {
        value: {
          ...window.location,
          pathname: '/auth/google/callback',
        },
        writable: true,
      });

      mockAuthAPI.getStatus.mockClear();

      renderWithAuth();

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
      });

      // Status should not be called on OAuth callback page
      expect(mockAuthAPI.getStatus).not.toHaveBeenCalled();

      // Reset location
      Object.defineProperty(window, 'location', {
        value: {
          ...window.location,
          pathname: '/',
        },
        writable: true,
      });
    });
  });
});
