/**
 * TDD Tests for RouteGuard Component
 * 
 * These tests verify the RouteGuard component logic following TDD principles.
 * Note: These are integration tests that verify the component behavior.
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { RouteGuard } from './RouteGuard';
import { useAuth } from '@/lib/auth/AuthContext';
import { useRouter } from 'next/navigation';
import type { AuthContextValue } from '@/lib/auth/AuthContext';
import type { SessionViewModel } from '@/lib/view-models/SessionViewModel';

// Mock dependencies
vi.mock('@/lib/auth/AuthContext');
vi.mock('next/navigation');

// Mock SessionViewModel factory
function createMockSession(overrides: Partial<SessionViewModel> = {}): SessionViewModel {
  return {
    isAuthenticated: true,
    user: {
      userId: 'user-123',
      email: 'test@example.com',
      displayName: 'Test User',
      ...overrides.user,
    },
    ...overrides,
  };
}

// Mock AuthContext factory
function createMockAuthContext(overrides: Partial<AuthContextValue> = {}): AuthContextValue {
  return {
    session: null,
    loading: false,
    login: vi.fn(),
    logout: vi.fn(),
    refreshSession: vi.fn(),
    ...overrides,
  };
}

describe('RouteGuard', () => {
  const mockUseAuth = vi.mocked(useAuth);
  const mockUseRouter = vi.mocked(useRouter);
  
  let mockRouter: { push: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockRouter = { push: vi.fn() };
    mockUseRouter.mockReturnValue(mockRouter as any);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('Authentication State', () => {
    it('should render children when user is authenticated', async () => {
      const mockAuthContext = createMockAuthContext({
        session: createMockSession(),
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(screen.getByTestId('protected-content')).toBeInTheDocument();
      });
    });

    it('should show loading state when auth context is loading', () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: true,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      // Should show loading state, not children
      expect(screen.queryByTestId('protected-content')).not.toBeInTheDocument();
    });

    it('should redirect when user is not authenticated', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(mockRouter.push).toHaveBeenCalledWith('/auth/login');
      });
    });
  });

  describe('Custom Configuration', () => {
    it('should use custom redirect path when specified', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard config={{ unauthorizedRedirectPath: '/custom-login' }}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(mockRouter.push).toHaveBeenCalledWith('/custom-login');
      });
    });

    it('should not redirect when redirectOnUnauthorized is false', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard config={{ redirectOnUnauthorized: false }}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      // Wait for any potential redirects
      await new Promise(resolve => setTimeout(resolve, 100));

      expect(mockRouter.push).not.toHaveBeenCalled();
    });

    it('should show unauthorized component when redirect is disabled', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      const unauthorizedComponent = <div data-testid="unauthorized">Access Denied</div>;

      render(
        <RouteGuard 
          config={{ redirectOnUnauthorized: false }}
          unauthorizedComponent={unauthorizedComponent}
        >
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(screen.getByTestId('unauthorized')).toBeInTheDocument();
      });
    });
  });

  describe('Custom Loading Component', () => {
    it('should show custom loading component when specified', () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: true,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      const loadingComponent = <div data-testid="custom-loading">Custom Loading...</div>;

      render(
        <RouteGuard loadingComponent={loadingComponent}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      expect(screen.getByTestId('custom-loading')).toBeInTheDocument();
      expect(screen.queryByTestId('protected-content')).not.toBeInTheDocument();
    });
  });

  describe('Role-Based Access', () => {
    it('should allow access when user has required role', async () => {
      const mockAuthContext = createMockAuthContext({
        session: createMockSession({
          user: {
            userId: 'user-123',
            email: 'admin@example.com',
            displayName: 'Admin User',
            role: 'admin',
          },
        }),
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard config={{ requiredRoles: ['admin'] }}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(screen.getByTestId('protected-content')).toBeInTheDocument();
      });
    });

    it('should redirect when user lacks required role', async () => {
      const mockAuthContext = createMockAuthContext({
        session: createMockSession({
          user: {
            userId: 'user-123',
            email: 'user@example.com',
            displayName: 'Regular User',
            role: 'user',
          },
        }),
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard config={{ requiredRoles: ['admin'] }}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(mockRouter.push).toHaveBeenCalledWith('/auth/login');
      });
    });
  });

  describe('Edge Cases', () => {
    it('should handle undefined session gracefully', async () => {
      const mockAuthContext = createMockAuthContext({
        session: undefined as any,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(mockRouter.push).toHaveBeenCalledWith('/auth/login');
      });
    });

    it('should handle empty required roles array', async () => {
      const mockAuthContext = createMockAuthContext({
        session: createMockSession(),
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard config={{ requiredRoles: [] }}>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(screen.getByTestId('protected-content')).toBeInTheDocument();
      });
    });

    it('should handle rapid session state changes', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: true,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      const { rerender } = render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      // Simulate session becoming available
      mockAuthContext.session = createMockSession();
      mockAuthContext.loading = false;
      
      rerender(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      await waitFor(() => {
        expect(screen.getByTestId('protected-content')).toBeInTheDocument();
      });
    });
  });

  describe('Redirect Timing', () => {
    it('should wait before redirecting (500ms delay)', async () => {
      const mockAuthContext = createMockAuthContext({
        session: null,
        loading: false,
      });
      mockUseAuth.mockReturnValue(mockAuthContext);

      render(
        <RouteGuard>
          <div data-testid="protected-content">Protected Content</div>
        </RouteGuard>
      );

      // Should not redirect immediately
      expect(mockRouter.push).not.toHaveBeenCalled();

      // Wait for the delay
      await waitFor(() => {
        expect(mockRouter.push).toHaveBeenCalledWith('/auth/login');
      }, { timeout: 1000 });
    });
  });
});