import { GUARDS_METADATA } from '@nestjs/common/constants';
import { Test, TestingModule } from '@nestjs/testing';
import { validate } from 'class-validator';
import { ServiceCredentialGuard } from '../common/guards/service-credential/service-credential.guard';
import { LookupUserDto } from './dto/lookup-user.dto';
import { ResolveUuidsByEmailsDto } from './dto/resolve-uuids-by-emails.dto';
import { ListMasterUsersQueryDto } from './dto/list-users-query.dto';
import { MasterApiController } from './master-api.controller';
import { MasterApiService } from './master-api.service';

describe('MasterApiController', () => {
  let controller: MasterApiController;
  let service: MasterApiService;

  beforeEach(async () => {
    const mockService = {
      resolveUuidsByEmails: jest.fn(),
      lookupUser: jest.fn(),
      getAdvisorUsageMetrics: jest.fn(),
      batchFetchUsers: jest.fn(),
      searchUsers: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [MasterApiController],
      providers: [
        {
          provide: MasterApiService,
          useValue: mockService,
        },
      ],
    })
      .overrideGuard(ServiceCredentialGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get(MasterApiController);
    service = module.get(MasterApiService);
  });

  it('forwards usage metrics uuid to the service', async () => {
    const uuid = 'b2c3d4e5-f6a7-4812-bcde-f12345678901';
    const expected = {
      metrics: {
        clients: 3,
        openFinanceConnections: 2,
        clientDirectoryStorageGb: 1.1,
      },
    };

    jest.spyOn(service, 'getAdvisorUsageMetrics').mockResolvedValue(expected);

    await expect(controller.getAdvisorUsageMetrics(uuid)).resolves.toEqual(
      expected,
    );
    expect(service.getAdvisorUsageMetrics).toHaveBeenCalledWith(uuid);
  });

  it('protects all routes with ServiceCredentialGuard at class level', () => {
    const guards = Reflect.getMetadata(GUARDS_METADATA, MasterApiController);

    expect(guards).toContain(ServiceCredentialGuard);
  });

  it('forwards lookup params to the service', async () => {
    const dto = {
      uuid: '00000000-0000-4000-8000-000000000001',
    } satisfies LookupUserDto;
    const expected = {
      user: {
        uuid: dto.uuid,
        name: 'Ada Lovelace',
        firstName: 'Ada',
        lastName: 'Lovelace',
        email: 'ada@planfi.dev',
        emailVerifiedAt: null,
        phone: '11988887777',
        phoneCountryCode: 'BR',
        phoneIsWhatsapp: false,
        whatsapp: '11988887777',
        whatsappCountryCode: 'BR',
        businessName: null,
        businessSubdomain: null,
        createdAt: '2024-01-01T12:00:00.000000Z',
        updatedAt: '2024-01-01T12:00:00.000000Z',
      },
    };

    jest.spyOn(service, 'lookupUser').mockResolvedValue(expected);

    await expect(controller.lookupUser(dto)).resolves.toEqual(expected);
    expect(service.lookupUser).toHaveBeenCalledWith({
      uuid: dto.uuid,
      email: undefined,
    });
  });

  it('accepts PlanFi UUIDs with underscores in lookup dto validation', async () => {
    const dto = Object.assign(new LookupUserDto(), {
      uuid: '3dedad84_f705_4f43_8c6e_9ca2503dd654',
    });

    await expect(validate(dto)).resolves.toHaveLength(0);
  });

  it('protects resolveUuidsByEmails via class-level ServiceCredentialGuard', () => {
    const guards = Reflect.getMetadata(GUARDS_METADATA, MasterApiController);

    expect(guards).toContain(ServiceCredentialGuard);
  });

  it('keeps the request contract and forwards emails to the service', async () => {
    const dto = {
      emails: ['ada@planfi.dev', 'grace@planfi.dev'],
    } satisfies ResolveUuidsByEmailsDto;
    const expected = {
      resolved: [{ email: 'ada@planfi.dev', uuid: 'uuid-1' }],
      notFound: ['grace@planfi.dev'],
      ambiguous: [],
    };

    jest.spyOn(service, 'resolveUuidsByEmails').mockResolvedValue(expected);

    await expect(controller.resolveUuidsByEmails(dto)).resolves.toEqual(
      expected,
    );
    expect(service.resolveUuidsByEmails).toHaveBeenCalledWith(dto.emails);
  });

  describe('searchUsers', () => {
    it('delegates to service.searchUsers with the query', async () => {
      const query: ListMasterUsersQueryDto = { search: 'Ada', perPage: 10 };
      const expected = {
        data: [
          {
            uuid: '00000000-0000-4000-8000-000000000001',
            name: 'Ada Lovelace',
            firstName: 'Ada',
            lastName: 'Lovelace',
            email: 'ada@planfi.dev',
            emailVerifiedAt: null,
            phone: null,
            phoneCountryCode: null,
            phoneIsWhatsapp: null,
            whatsapp: null,
            whatsappCountryCode: null,
            businessName: null,
            businessSubdomain: null,
            createdAt: '2024-01-01T12:00:00.000000Z',
            updatedAt: '2024-01-01T12:00:00.000000Z',
          },
        ],
      };

      jest.spyOn(service, 'searchUsers' as any).mockResolvedValue(expected);

      const result = await controller.searchUsers(query);
      expect(result).toEqual(expected);
      expect(service.searchUsers).toHaveBeenCalledWith(query);
    });

    it('protects searchUsers via class-level ServiceCredentialGuard', () => {
      const guards = Reflect.getMetadata(GUARDS_METADATA, MasterApiController);
      expect(guards).toContain(ServiceCredentialGuard);
    });
  });

  describe('batchFetchUsers', () => {
    it('delegates to service.batchFetchUsers', async () => {
      const expected = {
        foundByUuid: [],
        foundByEmail: [
          {
            requestedEmail: 'a@b.com',
            user: { uuid: 'u1', email: 'a@b.com', name: 'A' } as any,
          },
        ],
        notFoundUuids: [],
        notFoundEmails: [],
      };
      jest.spyOn(service, 'batchFetchUsers' as any).mockResolvedValue(expected);

      const result = await controller.batchFetchUsers({
        uuids: [],
        emails: ['a@b.com'],
      });
      expect(result).toEqual(expected);
      expect(service.batchFetchUsers).toHaveBeenCalledWith([], ['a@b.com']);
    });

    it('protects batchFetchUsers via class-level ServiceCredentialGuard', () => {
      const guards = Reflect.getMetadata(GUARDS_METADATA, MasterApiController);
      expect(guards).toContain(ServiceCredentialGuard);
    });

    it('skips the global per-IP throttler at class level (S2S endpoint with its own service-key auth)', () => {
      // @SkipThrottle() sets THROTTLER:SKIP<name> metadata; default throttler name is "default".
      expect(
        Reflect.getMetadata('THROTTLER:SKIPdefault', MasterApiController),
      ).toBe(true);
    });
  });
});
