import { describe, it, expect, vi, Mocked, beforeEach } from 'vitest';
import { RouteGuard } from './RouteGuard';
import { PathnameInterpreter } from './PathnameInterpreter';
import { RouteAccessPolicy } from './RouteAccessPolicy';
import { SessionGateway } from '../gateways/SessionGateway';
import { AuthRedirectBuilder } from './AuthRedirectBuilder';
import type { AuthSessionDTO } from '../types/generated/AuthSessionDTO';

// Mock dependencies
vi.mock('./PathnameInterpreter');
vi.mock('./RouteAccessPolicy');
vi.mock('../gateways/SessionGateway');
vi.mock('./AuthRedirectBuilder');

describe('RouteGuard', () => {
  let routeGuard: RouteGuard;
  let mockInterpreter: Mocked<PathnameInterpreter>;
  let mockPolicy: Mocked<RouteAccessPolicy>;
  let mockGateway: Mocked<SessionGateway>;
  let mockBuilder: Mocked<AuthRedirectBuilder>;

  beforeEach(() => {
    // Reset all mocks
    vi.clearAllMocks();
    
    // Create mock instances
    mockInterpreter = {
      interpret: vi.fn(),
    } as any;
    
    mockPolicy = {
      isPublic: vi.fn(),
      isAuthPage: vi.fn(),
      requiredRoles: vi.fn(),
    } as any;
    
    mockGateway = {
      getSession: vi.fn(),
    } as any;
    
    mockBuilder = {
      awayFromAuthPage: vi.fn(),
      toLogin: vi.fn(),
    } as any;
    
    // Create RouteGuard instance
    routeGuard = new RouteGuard(
      mockInterpreter,
      mockPolicy,
      mockGateway,
      mockBuilder
    );
  });

  describe('RED: public non-auth page → no redirect', () => {
    it('should allow access without redirect for public non-auth pages', async () => {
      // Arrange
      const pathname = '/public/page';
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/public/page' });
      mockPolicy.isPublic.mockReturnValue(true);
      mockPolicy.isAuthPage.mockReturnValue(false);

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockInterpreter.interpret).toHaveBeenCalledWith(pathname);
      expect(mockPolicy.isPublic).toHaveBeenCalledWith('/public/page');
      expect(mockPolicy.isAuthPage).toHaveBeenCalledWith('/public/page');
      expect(mockGateway.getSession).not.toHaveBeenCalled();
      expect(result).toEqual({ type: 'allow' });
    });
  });

  describe('auth page, no session → allow', () => {
    it('should allow access to auth page when no session exists', async () => {
      // Arrange
      const pathname = '/login';
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/login' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(true);
      mockGateway.getSession.mockResolvedValue(null);

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(result).toEqual({ type: 'allow' });
    });
  });

  describe('auth page, session → away redirect', () => {
    it('should redirect away from auth page when session exists', async () => {
      // Arrange
      const pathname = '/login';
      const mockSession: AuthSessionDTO = {
        user: { userId: '123', role: 'user', email: 'test@example.com', displayName: 'Test User' },
        token: 'mock-token',
      };
      
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/login' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(true);
      mockGateway.getSession.mockResolvedValue(mockSession);
      mockBuilder.awayFromAuthPage.mockReturnValue('/dashboard');

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(mockBuilder.awayFromAuthPage).toHaveBeenCalledWith({
        session: mockSession,
        currentPathname: '/login',
      });
      expect(result).toEqual({ type: 'redirect', to: '/dashboard' });
    });
  });

  describe('protected, no session → login redirect', () => {
    it('should redirect to login when accessing protected page without session', async () => {
      // Arrange
      const pathname = '/protected/dashboard';
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/protected/dashboard' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(false);
      mockGateway.getSession.mockResolvedValue(null);
      mockBuilder.toLogin.mockReturnValue('/login?redirect=/protected/dashboard');

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(mockBuilder.toLogin).toHaveBeenCalledWith({ currentPathname: '/protected/dashboard' });
      expect(result).toEqual({ type: 'redirect', to: '/login?redirect=/protected/dashboard' });
    });
  });

  describe('protected, wrong role → login', () => {
    it('should redirect to login when user lacks required role', async () => {
      // Arrange
      const pathname = '/admin/panel';
      const mockSession: AuthSessionDTO = {
        user: { userId: '123', role: 'user', email: 'test@example.com', displayName: 'Test User' },
        token: 'mock-token',
      };
      
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/admin/panel' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(false);
      mockGateway.getSession.mockResolvedValue(mockSession);
      mockPolicy.requiredRoles.mockReturnValue(['admin']);
      mockBuilder.toLogin.mockReturnValue('/login?redirect=/admin/panel');

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(mockPolicy.requiredRoles).toHaveBeenCalledWith('/admin/panel');
      expect(mockBuilder.toLogin).toHaveBeenCalledWith({ currentPathname: '/admin/panel' });
      expect(result).toEqual({ type: 'redirect', to: '/login?redirect=/admin/panel' });
    });
  });

  describe('protected, correct role → allow', () => {
    it('should allow access when user has required role', async () => {
      // Arrange
      const pathname = '/admin/panel';
      const mockSession: AuthSessionDTO = {
        user: { userId: '123', role: 'admin', email: 'test@example.com', displayName: 'Test User' },
        token: 'mock-token',
      };
      
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/admin/panel' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(false);
      mockGateway.getSession.mockResolvedValue(mockSession);
      mockPolicy.requiredRoles.mockReturnValue(['admin']);

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(mockPolicy.requiredRoles).toHaveBeenCalledWith('/admin/panel');
      expect(result).toEqual({ type: 'allow' });
    });

    it('should allow access when no specific roles required', async () => {
      // Arrange
      const pathname = '/dashboard';
      const mockSession: AuthSessionDTO = {
        user: { userId: '123', role: 'user', email: 'test@example.com', displayName: 'Test User' },
        token: 'mock-token',
      };
      
      mockInterpreter.interpret.mockReturnValue({ locale: 'en', logicalPathname: '/dashboard' });
      mockPolicy.isPublic.mockReturnValue(false);
      mockPolicy.isAuthPage.mockReturnValue(false);
      mockGateway.getSession.mockResolvedValue(mockSession);
      mockPolicy.requiredRoles.mockReturnValue(null);

      // Act
      const result = await routeGuard.enforce({ pathname });

      // Assert
      expect(mockGateway.getSession).toHaveBeenCalled();
      expect(mockPolicy.requiredRoles).toHaveBeenCalledWith('/dashboard');
      expect(result).toEqual({ type: 'allow' });
    });
  });
});
