import { describe, it, expect, vi, Mocked } from 'vitest';
import { AuthRedirectBuilder } from './AuthRedirectBuilder';
import { RouteAccessPolicy } from './RouteAccessPolicy';
import { ReturnToSanitizer } from './ReturnToSanitizer';
import { RoutePathBuilder } from './RoutePathBuilder';
import { PathnameInterpreter } from './PathnameInterpreter';
import type { AuthSessionDTO } from '../types/generated/AuthSessionDTO';

describe('AuthRedirectBuilder', () => {
  // Mock dependencies
  let mockPolicy: Mocked<RouteAccessPolicy>;
  let mockSanitizer: Mocked<ReturnToSanitizer>;
  let mockPathBuilder: Mocked<RoutePathBuilder>;
  let mockInterpreter: Mocked<PathnameInterpreter>;
  
  // System under test
  let builder: AuthRedirectBuilder;

  beforeEach(() => {
    // Create mock implementations
    mockPolicy = {
      roleHome: vi.fn(),
      roleHomeRouteId: vi.fn(),
    } as any;
    
    mockSanitizer = {
      sanitizeReturnTo: vi.fn(),
    } as any;
    
    mockPathBuilder = {
      build: vi.fn(),
    } as any;
    
    mockInterpreter = {
      interpret: vi.fn(),
    } as any;
    
    builder = new AuthRedirectBuilder(
      mockPolicy,
      mockSanitizer,
      mockPathBuilder,
      mockInterpreter
    );
  });

  describe('toLogin', () => {
    describe('without locale', () => {
      it('should build login path without locale and append returnTo', () => {
        // Arrange
        const currentPathname = '/dashboard';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/dashboard',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/dashboard');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(mockInterpreter.interpret).toHaveBeenCalledWith('/dashboard');
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'auth.login',
          {},
          { locale: null }
        );
        expect(mockSanitizer.sanitizeReturnTo).toHaveBeenCalledWith(
          '/dashboard',
          '/'
        );
        expect(result).toBe('/auth/login?returnTo=%2Fdashboard');
      });

      it('should handle root path as returnTo', () => {
        // Arrange
        const currentPathname = '/';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(result).toBe('/auth/login?returnTo=%2F');
      });
    });

    describe('with locale', () => {
      it('should build login path with locale and append returnTo', () => {
        // Arrange
        const currentPathname = '/de/dashboard';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'de',
          logicalPathname: '/dashboard',
        });
        mockPathBuilder.build.mockReturnValue('/de/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/dashboard');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(mockInterpreter.interpret).toHaveBeenCalledWith('/de/dashboard');
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'auth.login',
          {},
          { locale: 'de' }
        );
        expect(mockSanitizer.sanitizeReturnTo).toHaveBeenCalledWith(
          '/de/dashboard',
          '/'
        );
        expect(result).toBe('/de/auth/login?returnTo=%2Fdashboard');
      });

      it('should handle different locales', () => {
        // Arrange
        const currentPathname = '/fr/races/123';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'fr',
          logicalPathname: '/races/123',
        });
        mockPathBuilder.build.mockReturnValue('/fr/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/races/123');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(result).toBe('/fr/auth/login?returnTo=%2Fraces%2F123');
      });
    });

    describe('with invalid returnTo', () => {
      it('should use fallback when sanitizer returns fallback', () => {
        // Arrange
        const currentPathname = '/api/something';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/api/something',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(mockSanitizer.sanitizeReturnTo).toHaveBeenCalledWith(
          '/api/something',
          '/'
        );
        expect(result).toBe('/auth/login?returnTo=%2F');
      });

      it('should handle malicious URLs', () => {
        // Arrange
        const currentPathname = 'https://evil.com/phishing';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: 'https://evil.com/phishing',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(result).toBe('/auth/login?returnTo=%2F');
      });
    });

    describe('edge cases', () => {
      it('should handle empty currentPathname', () => {
        // Arrange
        const currentPathname = '';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(result).toBe('/auth/login?returnTo=%2F');
      });

      it('should handle paths with query strings', () => {
        // Arrange
        const currentPathname = '/dashboard?tab=settings';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/dashboard?tab=settings',
        });
        mockPathBuilder.build.mockReturnValue('/auth/login');
        mockSanitizer.sanitizeReturnTo.mockReturnValue('/dashboard?tab=settings');

        // Act
        const result = builder.toLogin({ currentPathname });

        // Assert
        expect(result).toBe('/auth/login?returnTo=%2Fdashboard%3Ftab%3Dsettings');
      });
    });
  });

  describe('awayFromAuthPage', () => {
    describe('with driver role', () => {
      it('should redirect to driver dashboard without locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-123',
            email: 'driver@example.com',
            displayName: 'Driver',
            role: 'driver',
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('dashboard');
        mockPathBuilder.build.mockReturnValue('/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockInterpreter.interpret).toHaveBeenCalledWith('/auth/login');
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('driver');
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'dashboard',
          {},
          { locale: null }
        );
        expect(result).toBe('/dashboard');
      });

      it('should redirect to driver dashboard with locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-123',
            email: 'driver@example.com',
            displayName: 'Driver',
            role: 'driver',
          },
        };
        const currentPathname = '/de/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'de',
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('dashboard');
        mockPathBuilder.build.mockReturnValue('/de/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'dashboard',
          {},
          { locale: 'de' }
        );
        expect(result).toBe('/de/dashboard');
      });
    });

    describe('with sponsor role', () => {
      it('should redirect to sponsor dashboard without locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-456',
            email: 'sponsor@example.com',
            displayName: 'Sponsor',
            role: 'sponsor',
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('sponsor.dashboard');
        mockPathBuilder.build.mockReturnValue('/sponsor/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('sponsor');
        expect(result).toBe('/sponsor/dashboard');
      });

      it('should redirect to sponsor dashboard with locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-456',
            email: 'sponsor@example.com',
            displayName: 'Sponsor',
            role: 'sponsor',
          },
        };
        const currentPathname = '/fr/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'fr',
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('sponsor.dashboard');
        mockPathBuilder.build.mockReturnValue('/fr/sponsor/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'sponsor.dashboard',
          {},
          { locale: 'fr' }
        );
        expect(result).toBe('/fr/sponsor/dashboard');
      });
    });

    describe('with admin role', () => {
      it('should redirect to admin dashboard without locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-789',
            email: 'admin@example.com',
            displayName: 'Admin',
            role: 'admin',
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('admin');
        mockPathBuilder.build.mockReturnValue('/admin');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('admin');
        expect(result).toBe('/admin');
      });

      it('should redirect to admin dashboard with locale', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-789',
            email: 'admin@example.com',
            displayName: 'Admin',
            role: 'admin',
          },
        };
        const currentPathname = '/es/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'es',
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('admin');
        mockPathBuilder.build.mockReturnValue('/es/admin');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPathBuilder.build).toHaveBeenCalledWith(
          'admin',
          {},
          { locale: 'es' }
        );
        expect(result).toBe('/es/admin');
      });
    });

    describe('with owner role', () => {
      it('should redirect to admin dashboard (owner maps to admin)', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-owner',
            email: 'owner@example.com',
            displayName: 'Owner',
            role: 'owner',
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('admin');
        mockPathBuilder.build.mockReturnValue('/admin');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('owner');
        expect(result).toBe('/admin');
      });
    });

    describe('edge cases', () => {
      it('should handle missing role (defaults to /dashboard)', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-no-role',
            email: 'norole@example.com',
            displayName: 'NoRole',
            // role is undefined
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('dashboard');
        mockPathBuilder.build.mockReturnValue('/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('');
        expect(result).toBe('/dashboard');
      });

      it('should handle empty role string', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-empty-role',
            email: 'emptyrole@example.com',
            displayName: 'EmptyRole',
            role: '',
          },
        };
        const currentPathname = '/auth/login';
        mockInterpreter.interpret.mockReturnValue({
          locale: null,
          logicalPathname: '/auth/login',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('dashboard');
        mockPathBuilder.build.mockReturnValue('/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockPolicy.roleHomeRouteId).toHaveBeenCalledWith('');
        expect(result).toBe('/dashboard');
      });

      it('should handle paths with locale and complex paths', () => {
        // Arrange
        const session: AuthSessionDTO = {
          token: 'test-token',
          user: {
            userId: 'user-123',
            email: 'driver@example.com',
            displayName: 'Driver',
            role: 'driver',
          },
        };
        const currentPathname = '/de/leagues/123/roster/admin';
        mockInterpreter.interpret.mockReturnValue({
          locale: 'de',
          logicalPathname: '/leagues/123/roster/admin',
        });
        mockPolicy.roleHomeRouteId.mockReturnValue('dashboard');
        mockPathBuilder.build.mockReturnValue('/de/dashboard');

        // Act
        const result = builder.awayFromAuthPage({ session, currentPathname });

        // Assert
        expect(mockInterpreter.interpret).toHaveBeenCalledWith('/de/leagues/123/roster/admin');
        expect(result).toBe('/de/dashboard');
      });
    });
  });
});
