/**
 * GoogleCallback Page Tests
 *
 * Tests for the Google OAuth callback page that handles
 * the authorization code exchange after Google Sign-In.
 */

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { vi, type MockedFunction } from 'vitest';
import { MemoryRouter, Routes, Route } from 'react-router';
import GoogleCallback from '../../pages/GoogleCallback';
import { useAuth } from '../../contexts/AuthContext';
import { fetchCSRFToken } from '../../services/api';

// Mock the dependencies
vi.mock('../../contexts/AuthContext');
vi.mock('../../services/api', () => ({
  fetchCSRFToken: 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 mockedFetchCSRFToken = fetchCSRFToken as MockedFunction<typeof fetchCSRFToken>;

// Helper to render GoogleCallback with route params
const renderGoogleCallback = (searchParams: string = '') => {
  return render(
    <MemoryRouter initialEntries={[`/auth/google/callback${searchParams}`]}>
      <Routes>
        <Route path="/auth/google/callback" element={<GoogleCallback />} />
      </Routes>
    </MemoryRouter>
  );
};

describe('GoogleCallback', () => {
  const mockGoogleLogin = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();

    mockedUseAuth.mockReturnValue({
      user: null,
      loading: false,
      showAuthModal: false,
      authModalMode: 'login',
      login: vi.fn(),
      register: vi.fn(),
      googleLogin: mockGoogleLogin,
      logout: vi.fn(),
      checkAuth: vi.fn(),
      updateUser: vi.fn(),
      openAuthModal: vi.fn(),
      closeAuthModal: vi.fn(),
    });

    mockedFetchCSRFToken.mockResolvedValue('test-csrf-token');
    mockGoogleLogin.mockResolvedValue({});
  });

  describe('Loading State', () => {
    it('renders loading state initially', () => {
      renderGoogleCallback('?code=valid-auth-code');

      expect(screen.getByRole('status')).toBeInTheDocument();
      expect(screen.getByText('Completing Google Sign-In...')).toBeInTheDocument();
      expect(screen.getByText('Please wait while we log you in.')).toBeInTheDocument();
    });
  });

  describe('Successful Authentication', () => {
    it('extracts authorization code from URL params', async () => {
      renderGoogleCallback('?code=test-auth-code-123');

      await waitFor(() => {
        expect(mockedFetchCSRFToken).toHaveBeenCalled();
        expect(mockGoogleLogin).toHaveBeenCalledWith('test-auth-code-123');
      });
    });

    it('calls fetchCSRFToken then googleLogin with code', async () => {
      renderGoogleCallback('?code=valid-code');

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

      await waitFor(() => {
        expect(mockGoogleLogin).toHaveBeenCalledWith('valid-code');
      });
    });

    it('navigates to "/" on successful login', async () => {
      renderGoogleCallback('?code=valid-code');

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

  describe('Error States', () => {
    it('displays error when OAuth error param present', async () => {
      renderGoogleCallback('?error=access_denied');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
        expect(screen.getByText('Google Sign-In was cancelled or failed. Please try again.')).toBeInTheDocument();
      });

      expect(mockGoogleLogin).not.toHaveBeenCalled();
    });

    it('displays error when code is missing', async () => {
      renderGoogleCallback();

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
        expect(screen.getByText('No authorization code received from Google.')).toBeInTheDocument();
      });

      expect(mockGoogleLogin).not.toHaveBeenCalled();
    });

    it('displays error when googleLogin fails', async () => {
      mockGoogleLogin.mockRejectedValue({
        response: {
          data: { error: 'Invalid authorization code' },
        },
      });

      renderGoogleCallback('?code=invalid-code');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
        expect(screen.getByText('Invalid authorization code')).toBeInTheDocument();
      });
    });

    it('displays generic error when googleLogin fails without specific message', async () => {
      mockGoogleLogin.mockRejectedValue(new Error('Network error'));

      renderGoogleCallback('?code=valid-code');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
        expect(screen.getByText('Failed to sign in with Google. Please try again.')).toBeInTheDocument();
      });
    });

    it('displays error when CSRF token fetch fails', async () => {
      mockedFetchCSRFToken.mockResolvedValue('');

      renderGoogleCallback('?code=valid-code');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
        expect(screen.getByText('Failed to initialize security token. Please try again.')).toBeInTheDocument();
      });

      expect(mockGoogleLogin).not.toHaveBeenCalled();
    });
  });

  describe('Error Recovery', () => {
    it('renders return to home button on error', async () => {
      renderGoogleCallback('?error=access_denied');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
      });

      const returnButton = screen.getByRole('button', { name: /return to home/i });
      expect(returnButton).toBeInTheDocument();
    });

    it('navigates home when return button clicked', async () => {
      renderGoogleCallback('?error=access_denied');

      await waitFor(() => {
        expect(screen.getByText('Sign-In Failed')).toBeInTheDocument();
      });

      const returnButton = screen.getByRole('button', { name: /return to home/i });
      returnButton.click();

      expect(mockNavigate).toHaveBeenCalledWith('/');
    });
  });

  describe('StrictMode Double-Execution Prevention', () => {
    it('prevents double processing with useRef', async () => {
      // First render
      const { unmount } = renderGoogleCallback('?code=valid-code');

      await waitFor(() => {
        expect(mockGoogleLogin).toHaveBeenCalledTimes(1);
      });

      // Unmount and remount (simulating StrictMode behavior)
      unmount();

      // The component uses useRef to track hasProcessed, so multiple
      // rapid re-renders within the same instance should not cause double calls
      expect(mockGoogleLogin).toHaveBeenCalledTimes(1);
    });
  });

  describe('UI Elements', () => {
    it('shows spinner during processing', () => {
      renderGoogleCallback('?code=valid-code');

      const spinner = screen.getByRole('status');
      expect(spinner).toHaveClass('spinner-border');
    });

    it('shows error icon on failure', async () => {
      renderGoogleCallback('?error=access_denied');

      await waitFor(() => {
        const errorIcon = document.querySelector('.bi-exclamation-circle');
        expect(errorIcon).toBeInTheDocument();
      });
    });
  });
});
