import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { MultiAuthGuard } from './multi-auth.guard';
import { ApiKeyGuard } from '../api-key/api-key.guard';
import { ClientJwtGuard } from '../../../auth/guards/client-jwt.guard';
import { UserJwtGuard } from '../../../auth/guards/user-jwt.guard';

describe('MultiAuthGuard', () => {
  let guard: MultiAuthGuard;
  let apiKeyGuard: ApiKeyGuard;
  let clientJwtGuard: ClientJwtGuard;
  let userJwtGuard: UserJwtGuard;

  const mockExecutionContext = {
    switchToHttp: jest.fn().mockReturnThis(),
    getRequest: jest.fn().mockReturnThis(),
    getResponse: jest.fn().mockReturnThis(),
    getNext: jest.fn().mockReturnThis(),
    getHandler: jest.fn(),
    getClass: jest.fn(),
    getArgs: jest.fn(),
    getArgByIndex: jest.fn(),
    switchToRpc: jest.fn(),
    switchToWs: jest.fn(),
    getType: jest.fn(),
  } as unknown as ExecutionContext;

  const mockApiKeyGuard = {
    canActivate: jest.fn(),
  };

  const mockClientJwtGuard = {
    canActivate: jest.fn(),
  };

  const mockUserJwtGuard = {
    canActivate: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MultiAuthGuard,
        {
          provide: ApiKeyGuard,
          useValue: mockApiKeyGuard,
        },
        {
          provide: ClientJwtGuard,
          useValue: mockClientJwtGuard,
        },
        {
          provide: UserJwtGuard,
          useValue: mockUserJwtGuard,
        },
      ],
    }).compile();

    guard = module.get<MultiAuthGuard>(MultiAuthGuard);
    apiKeyGuard = module.get<ApiKeyGuard>(ApiKeyGuard);
    clientJwtGuard = module.get<ClientJwtGuard>(ClientJwtGuard);
    userJwtGuard = module.get<UserJwtGuard>(UserJwtGuard);
  });

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

  it('should be defined', () => {
    expect(guard).toBeDefined();
  });

  describe('canActivate', () => {
    it('should return true when ApiKeyGuard succeeds', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(true);
      mockClientJwtGuard.canActivate.mockResolvedValue(false);
      mockUserJwtGuard.canActivate.mockResolvedValue(false);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).not.toHaveBeenCalled();
      expect(mockUserJwtGuard.canActivate).not.toHaveBeenCalled();
    });

    it('should return true when ClientJwtGuard succeeds', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockResolvedValue(true);
      mockUserJwtGuard.canActivate.mockResolvedValue(false);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockUserJwtGuard.canActivate).not.toHaveBeenCalled();
    });

    it('should return true when UserJwtGuard succeeds', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockResolvedValue(false);
      mockUserJwtGuard.canActivate.mockResolvedValue(true);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockUserJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
    });

    it('should return true when first guard succeeds, ignoring others', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(true);
      mockClientJwtGuard.canActivate.mockResolvedValue(true);
      mockUserJwtGuard.canActivate.mockResolvedValue(true);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).not.toHaveBeenCalled();
      expect(mockUserJwtGuard.canActivate).not.toHaveBeenCalled();
    });

    it('should throw UnauthorizedException when all guards fail', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockResolvedValue(false);
      mockUserJwtGuard.canActivate.mockResolvedValue(false);

      try {
        await guard.canActivate(mockExecutionContext);
        fail('Expected UnauthorizedException to be thrown');
      } catch (error) {
        expect(error).toBeInstanceOf(UnauthorizedException);
        expect(error.message).toContain(
          'Acesso negado: nenhum método de autenticação válido encontrado',
        );
        expect(error.message).toContain(
          'Use API Key (x-api-key header) ou JWT Bearer token (client ou user)',
        );
      }
    });

    it('should throw UnauthorizedException when all guards throw errors', async () => {
      const apiKeyError = new Error('Invalid API key');
      const clientJwtError = new Error('Invalid client JWT');
      const userJwtError = new Error('Invalid user JWT');

      mockApiKeyGuard.canActivate.mockRejectedValue(apiKeyError);
      mockClientJwtGuard.canActivate.mockRejectedValue(clientJwtError);
      mockUserJwtGuard.canActivate.mockRejectedValue(userJwtError);

      try {
        await guard.canActivate(mockExecutionContext);
        fail('Expected UnauthorizedException to be thrown');
      } catch (error) {
        expect(error).toBeInstanceOf(UnauthorizedException);
        expect(error.message).toContain(
          'Acesso negado: nenhum método de autenticação válido encontrado',
        );
        expect(error.message).toContain('Invalid API key');
        expect(error.message).toContain('Invalid client JWT');
        expect(error.message).toContain('Invalid user JWT');
      }
    });

    it('should handle mixed results (some false, some errors)', async () => {
      const clientJwtError = new Error('Invalid client JWT');
      const userJwtError = new Error('Invalid user JWT');

      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockRejectedValue(clientJwtError);
      mockUserJwtGuard.canActivate.mockRejectedValue(userJwtError);

      try {
        await guard.canActivate(mockExecutionContext);
        fail('Expected UnauthorizedException to be thrown');
      } catch (error) {
        expect(error).toBeInstanceOf(UnauthorizedException);
        expect(error.message).toContain(
          'Acesso negado: nenhum método de autenticação válido encontrado',
        );
        expect(error.message).toContain('Invalid client JWT');
        expect(error.message).toContain('Invalid user JWT');
      }
    });

    it('should handle non-Error exceptions', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockRejectedValue('String error');
      mockUserJwtGuard.canActivate.mockRejectedValue({
        message: 'Object error',
      });

      try {
        await guard.canActivate(mockExecutionContext);
        fail('Expected UnauthorizedException to be thrown');
      } catch (error) {
        expect(error).toBeInstanceOf(UnauthorizedException);
        expect(error.message).toContain(
          'Acesso negado: nenhum método de autenticação válido encontrado',
        );
        expect(error.message).toContain('Erro de autenticação');
      }
    });

    it('should succeed when second guard succeeds after first fails', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockResolvedValue(true);
      mockUserJwtGuard.canActivate.mockResolvedValue(false);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockUserJwtGuard.canActivate).not.toHaveBeenCalled();
    });

    it('should succeed when third guard succeeds after first two fail', async () => {
      mockApiKeyGuard.canActivate.mockResolvedValue(false);
      mockClientJwtGuard.canActivate.mockResolvedValue(false);
      mockUserJwtGuard.canActivate.mockResolvedValue(true);

      const result = await guard.canActivate(mockExecutionContext);

      expect(result).toBe(true);
      expect(mockApiKeyGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockClientJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
      expect(mockUserJwtGuard.canActivate).toHaveBeenCalledWith(
        mockExecutionContext,
      );
    });
  });
});
