import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ServiceAuthGuard } from '../../../src/shared/guards/service-auth.guard';
import {
  generateServiceToken,
  generateExpiredServiceToken,
  TEST_SERVICE_JWT_SECRET,
} from '../../utils/test-utils';

describe('ServiceAuthGuard', () => {
  let guard: ServiceAuthGuard;

  const createMockContext = (headers: Record<string, string> = {}, connection: any = {}): ExecutionContext => {
    const mockRequest = {
      headers,
      connection,
      socket: connection,
    };

    return {
      switchToHttp: () => ({
        getRequest: () => mockRequest,
      }),
    } as ExecutionContext;
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ServiceAuthGuard,
        {
          provide: ConfigService,
          useValue: {
            get: (key: string) => {
              switch (key) {
                case 'SERVICE_JWT_SECRET':
                  return TEST_SERVICE_JWT_SECRET;
                case 'ALLOWED_SERVICES':
                  return 'identity-service,recovery-service';
                default:
                  return undefined;
              }
            },
          },
        },
      ],
    }).compile();

    guard = module.get<ServiceAuthGuard>(ServiceAuthGuard);
  });

  describe('canActivate', () => {
    it('should allow request with valid identity-service token', () => {
      const token = generateServiceToken('identity-service');
      const context = createMockContext({ 'x-service-token': token });

      expect(guard.canActivate(context)).toBe(true);
    });

    it('should allow request with valid recovery-service token', () => {
      const token = generateServiceToken('recovery-service');
      const context = createMockContext({ 'x-service-token': token });

      expect(guard.canActivate(context)).toBe(true);
    });

    it('should reject request without service token', () => {
      const context = createMockContext({});

      expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
      expect(() => guard.canActivate(context)).toThrow('Missing service token');
    });

    it('should reject request with invalid token', () => {
      const context = createMockContext({ 'x-service-token': 'invalid-token' });

      expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
      expect(() => guard.canActivate(context)).toThrow('Invalid service token');
    });

    it('should reject request with expired token', () => {
      const token = generateExpiredServiceToken('identity-service');
      const context = createMockContext({ 'x-service-token': token });

      expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
    });

    it('should reject request from unauthorized service', () => {
      const token = generateServiceToken('unknown-service');
      const context = createMockContext({ 'x-service-token': token });

      expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
      expect(() => guard.canActivate(context)).toThrow('Service not authorized');
    });

    it('should reject token signed with wrong secret', () => {
      const token = generateServiceToken('identity-service', 'wrong-secret');
      const context = createMockContext({ 'x-service-token': token });

      expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
    });

    it('should attach sourceService to request', () => {
      const token = generateServiceToken('identity-service');
      const mockRequest: any = {
        headers: { 'x-service-token': token },
        connection: {},
        socket: {},
      };
      const context = {
        switchToHttp: () => ({
          getRequest: () => mockRequest,
        }),
      } as ExecutionContext;

      guard.canActivate(context);

      expect(mockRequest.sourceService).toBe('identity-service');
    });

    it('should extract client IP from x-forwarded-for header', () => {
      const token = generateServiceToken('identity-service');
      const mockRequest: any = {
        headers: {
          'x-service-token': token,
          'x-forwarded-for': '203.0.113.195, 70.41.3.18, 150.172.238.178',
        },
        connection: {},
        socket: {},
      };
      const context = {
        switchToHttp: () => ({
          getRequest: () => mockRequest,
        }),
      } as ExecutionContext;

      guard.canActivate(context);

      expect(mockRequest.sourceIp).toBe('203.0.113.195');
    });

    it('should extract client IP from x-real-ip header', () => {
      const token = generateServiceToken('identity-service');
      const mockRequest: any = {
        headers: {
          'x-service-token': token,
          'x-real-ip': '192.168.1.100',
        },
        connection: {},
        socket: {},
      };
      const context = {
        switchToHttp: () => ({
          getRequest: () => mockRequest,
        }),
      } as ExecutionContext;

      guard.canActivate(context);

      expect(mockRequest.sourceIp).toBe('192.168.1.100');
    });

    it('should extract client IP from connection.remoteAddress', () => {
      const token = generateServiceToken('identity-service');
      const mockRequest: any = {
        headers: { 'x-service-token': token },
        connection: { remoteAddress: '10.0.0.1' },
        socket: {},
      };
      const context = {
        switchToHttp: () => ({
          getRequest: () => mockRequest,
        }),
      } as ExecutionContext;

      guard.canActivate(context);

      expect(mockRequest.sourceIp).toBe('10.0.0.1');
    });

    it('should return "unknown" when no IP can be determined', () => {
      const token = generateServiceToken('identity-service');
      const mockRequest: any = {
        headers: { 'x-service-token': token },
        connection: {},
        socket: {},
      };
      const context = {
        switchToHttp: () => ({
          getRequest: () => mockRequest,
        }),
      } as ExecutionContext;

      guard.canActivate(context);

      expect(mockRequest.sourceIp).toBe('unknown');
    });
  });
});
