import { EventEmitterModule } from '@nestjs/event-emitter';
import { Test, TestingModule } from '@nestjs/testing';
import { AppService } from '../../../app.service';
import { EEnvVariable } from '../../../configuration/enums/env-variable.enum';
import { RedisService } from '../../../shared/services/redis/redis.service';
import { MockedRedisService } from '../../../tests/mocked/services/mocked-redis.service';
import { MockedS3Service } from '../../../tests/mocked/services/mocked-s3.service';
import { Attachment } from '../../attachment.entity';
import { RemoveAttachmentWorkflow } from '../../workflows/remove-attachment/remove-attachment.workflow';
import { RemoveNotAllowedAttachmentsCron } from './remove-not-allowed-attachments.cron';

describe('RemoveNotAllowedAttachmentsService', () => {
  let service: RemoveNotAllowedAttachmentsCron;
  let removeAttachmentWorkflow: RemoveAttachmentWorkflow;
  let redisService: RedisService;
  let appService: AppService;

  beforeEach(async () => {
    process.env[EEnvVariable.MAX_TEMPORARY_ATTACHMENTS] = '5';

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MockedS3Service,
        RemoveAttachmentWorkflow,
        RemoveNotAllowedAttachmentsCron,
        MockedRedisService,
        {
          provide: AppService,
          useValue: { isMaster: null },
        },
      ],
      imports: [EventEmitterModule.forRoot()],
    }).compile();

    service = module.get<RemoveNotAllowedAttachmentsCron>(
      RemoveNotAllowedAttachmentsCron
    );
    removeAttachmentWorkflow = module.get<RemoveAttachmentWorkflow>(
      RemoveAttachmentWorkflow
    );
    redisService = module.get<RedisService>(RedisService);
    appService = module.get<AppService>(AppService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should skip executing if not in master process', async () => {
    // Set `isMaster` value of `AppService` to false so the cron shouldn't execute
    ((appService.isMaster as unknown) as boolean) = false;

    // Spy on the first call in `handleCron` method
    const firstMethodSpy = jest.spyOn(redisService, 'getList');

    // Call the method
    await service.handleCron();

    // Test
    expect(firstMethodSpy).not.toHaveBeenCalled();
  });

  it('should execute if in dev mode', async () => {
    // Set `isMaster` value of `AppService` to false so the cron shouldn't execute by the master-worker rule
    ((appService.isMaster as unknown) as boolean) = false;

    // Set "NODE_ENV" environment variable property so the cron should execute
    process.env['NODE_ENV'] = 'development';

    // Mock first method to return a valid value so the code wont fail
    redisService.getList = jest.fn().mockResolvedValue([]);

    // Call the method
    await service.handleCron();

    // Test
    expect(redisService.getList).toHaveBeenCalled();
  });

  it('should stop executing if no licenseeIds received from Redis', async () => {
    redisService.getList = jest.fn().mockResolvedValue([]);
    const spy = jest.spyOn(Attachment, 'find');

    // Set `isMaster` value of `AppService` to false so the cron should execute
    ((appService.isMaster as unknown) as boolean) = true;

    await service.handleCron();

    expect(spy).not.toHaveBeenCalled();
  });

  it('should delete nothing if less than MAX_TEMPORARY_ATTACHMENTS were received', async () => {
    // Mock 'getList' response so the logic will go through.
    // It doesnt really matter what we're returning here since `Attachment.find` will mocked anyways
    redisService.getList = jest.fn().mockResolvedValue(['1']);

    Attachment.find = jest
      .fn()
      .mockResolvedValue([
        new Attachment(),
        new Attachment(),
        new Attachment(),
      ]);

    const attachmentRemoveSpy = jest.spyOn(removeAttachmentWorkflow, 'remove');

    // Set `isMaster` value of `AppService` to false so the cron should execute
    ((appService.isMaster as unknown) as boolean) = true;

    await service.handleCron();

    expect(attachmentRemoveSpy).not.toBeCalled();
  });

  it('should remove only the first Attachments received', async () => {
    // Set "NODE_ENV" environment variable property so the cron should execute
    process.env['NODE_ENV'] = 'development';

    // Mock 'getList' response so the logic will go through.
    // It doesnt really matter what we're returning here since `Attachment.find` will mocked anyways
    redisService.getList = jest.fn().mockResolvedValue(['1']);

    redisService.deleteKey = jest.fn().mockReturnValue(undefined);

    const attachmentsMock = [
      new Attachment(),
      new Attachment(),
      new Attachment(),
      new Attachment(),
      new Attachment(),
      new Attachment(),
      new Attachment(),
    ];
    Attachment.find = jest.fn().mockResolvedValue(attachmentsMock);

    removeAttachmentWorkflow.remove = jest.fn().mockResolvedValue(undefined);

    await service.handleCron();

    const deleteCount =
      attachmentsMock.length - service.maxTemporaryAttachments;
    for (let i = 0; i < deleteCount; i++) {
      expect(removeAttachmentWorkflow.remove).toHaveBeenNthCalledWith(
        i + 1,
        attachmentsMock[i]
      );
    }

    expect(removeAttachmentWorkflow.remove).toBeCalledTimes(deleteCount);
  });
});
