import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { HttpException, HttpStatus } from '@nestjs/common';
import { DeleteResult, Repository, UpdateResult } from 'typeorm';
import { CohortEntity } from './entities/cohort.entity';
import { CohortService } from './cohort.service';
import { CreateCohortDto } from './dto/create-cohort.dto';
import { UpdateCohortDto } from './dto/update-cohort.dto';

describe('CohortService', () => {
  let service: CohortService;
  let cohortRepository: Repository<CohortEntity>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        CohortService,
        {
          provide: getRepositoryToken(CohortEntity),
          useValue: {
            get: jest.fn(),
            post: jest.fn(),
            patch: jest.fn(),
            put: jest.fn(),
            delete: jest.fn(),
            create: jest.fn(),
            insert: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            update: jest.fn(),
            createQueryBuilder: jest.fn(),
          },
        },
      ],
    }).compile();

    service = module.get<CohortService>(CohortService);
    cohortRepository = module.get<Repository<CohortEntity>>(
      getRepositoryToken(CohortEntity),
    );
  });

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

  describe('create', () => {
    it('should create a new cohort', async () => {
      const createCohortDto: Partial<CreateCohortDto> = {
        cohortId: '1',
        cohortName: 'Test Cohort',
        cohortGroupId: 1,
        recordDateTime: new Date(),
        companyTenantId: 360,
      };
      const cohortEntity = new CohortEntity();
      jest.spyOn(cohortRepository, 'create').mockReturnValue(cohortEntity);
      jest.spyOn(service, 'findOne').mockResolvedValue(cohortEntity);
      jest.spyOn(cohortRepository, 'insert').mockResolvedValue({
        identifiers: [{ cohortPk: 1 }],
      } as any);

      const result = await service.create(createCohortDto as CohortEntity);

      expect(result).not.toBeNull();
    });

    it('should throw an HttpException if the cohort creation fails', async () => {
      const createCohortDto: Partial<CreateCohortDto> = {
        cohortId: '1',
        cohortName: 'Test Cohort',
        cohortGroupId: 1,
      };
      const error = new Error('Database error');
      jest
        .spyOn(cohortRepository, 'create')
        .mockReturnValue(new CohortEntity() as any);
      jest.spyOn(cohortRepository, 'insert').mockRejectedValue(error);

      await expect(
        service.create(createCohortDto as CreateCohortDto),
      ).rejects.toThrow(
        new HttpException(error.message, HttpStatus.BAD_REQUEST),
      );
    });
  });

  describe('findAll', () => {
    it('should return all cohorts', async () => {
      const cohortEntities = [new CohortEntity(), new CohortEntity()];
      jest.spyOn(cohortRepository, 'find').mockResolvedValue(cohortEntities);

      const result = await service.findAll();

      expect(result).toEqual(cohortEntities);
    });
  });

  describe('findOne', () => {
    it('should return a cohort by id', async () => {
      const cohortEntity = new CohortEntity();
      jest.spyOn(cohortRepository, 'findOne').mockResolvedValue(cohortEntity);

      const result = await service.findOne(1);

      expect(result).toEqual(cohortEntity);
      expect(cohortRepository.findOne).toHaveBeenCalledWith({
        where: { cohortPk: 1 },
      });
    });
  });

  describe('update', () => {
    it('should update a cohort by id', async () => {
      const updateCohortDto: UpdateCohortDto = {
        cohortName: 'Updated Cohort',
      };
      const cohortEntity = new CohortEntity();
      jest
        .spyOn(cohortRepository, 'update')
        .mockResolvedValue({} as UpdateResult);
      jest.spyOn(cohortRepository, 'findOne').mockResolvedValue(cohortEntity);

      const result = await service.update(1, updateCohortDto);

      expect(result).toEqual(cohortEntity);
      expect(cohortRepository.update).toHaveBeenCalledWith(
        { cohortPk: 1 },
        updateCohortDto,
      );
      expect(cohortRepository.findOne).toHaveBeenCalledWith({
        where: { cohortPk: 1 },
      });
    });

    it('should throw an HttpException if the cohort update fails', async () => {
      const updateCohortDto: UpdateCohortDto = {
        cohortName: 'Updated Cohort',
      };
      const error = new Error('Database error');
      jest.spyOn(cohortRepository, 'update').mockRejectedValue(error);

      await expect(service.update(1, updateCohortDto)).rejects.toThrow(
        new HttpException(error.message, HttpStatus.BAD_REQUEST),
      );
    });
  });

  describe('remove', () => {
    it('should remove a cohort by id', async () => {
      jest
        .spyOn(cohortRepository, 'delete')
        .mockResolvedValue({ affected: 1 } as DeleteResult);

      const result = await service.remove(1);

      expect(result).toEqual({ deleted: true });
      expect(cohortRepository.delete).toHaveBeenCalledWith({
        cohortPk: 1,
      });
    });
  });

  describe('findCohortGroup', () => {
    describe('findCohortGroup', () => {
      it('should return cohort groups with employee counts', async () => {
        const cohortGroups = [
          { cohortGroupName: 'Group 1', cohortGroupId: 1 },
          { cohortGroupName: 'Group 2', cohortGroupId: 2 },
        ];
        const cohortEmp = [
          { cohortPK: 1, employeeCount: 10 },
          { cohortPK: 2, employeeCount: 20 },
        ];
        const queryBuilder = {
          select: jest.fn().mockReturnThis(),
          addSelect: jest.fn().mockReturnThis(),
          where: jest.fn().mockReturnThis(),
          innerJoin: jest.fn().mockReturnThis(),
          groupBy: jest.fn().mockReturnThis(),
          printSql: jest.fn().mockReturnThis(),
          getRawMany: jest.fn().mockResolvedValue(cohortGroups),
          andWhere: jest.fn().mockReturnThis(),
        };
        jest
          .spyOn(cohortRepository, 'createQueryBuilder')
          .mockReturnValue(queryBuilder as any);
        jest
          .spyOn(service, 'findCohortEmpByGroup')
          .mockResolvedValue(cohortEmp);

        const result = await service.findCohortGroup();

        expect(result).not.toBeNull();
        expect(cohortRepository.createQueryBuilder).toHaveBeenCalledWith(
          'STG_Cohort',
        );
        expect(service.findCohortEmpByGroup).toHaveBeenCalledWith('Group 1');
        expect(service.findCohortEmpByGroup).toHaveBeenCalledWith('Group 2');
      });
    });

    describe('findCohortGroupByName', () => {
      it('should return a cohort group by name', async () => {
        const cohortEntity = new CohortEntity();
        jest.spyOn(cohortRepository, 'findOne').mockResolvedValue(cohortEntity);

        const result = await service.findCohortGroupByName();

        expect(result).toEqual(cohortEntity);
        expect(cohortRepository.findOne).toHaveBeenCalledWith({
          where: { cohortId: '0', cohortName: '' },
        });
      });
    });

    describe('findCohortEmpByGroup', () => {
      it('should return cohort employees by group name', async () => {
        const cohortEmp = [
          {
            cohortPK: 1,
            groupName: 'Group 1',
            cohortID: '1',
            cohortName: 'Cohort 1',
            employeeCount: 10,
          },
          {
            cohortPK: 2,
            groupName: 'Group 1',
            cohortID: '2',
            cohortName: 'Cohort 2',
            employeeCount: 20,
          },
        ];
        jest.spyOn(cohortRepository, 'createQueryBuilder').mockReturnValue({
          select: jest.fn().mockReturnThis(),
          addSelect: jest.fn().mockReturnThis(),
          where: jest.fn().mockReturnThis(),
          innerJoin: jest.fn().mockReturnThis(),
          groupBy: jest.fn().mockReturnThis(),
          printSql: jest.fn().mockReturnThis(),
          getRawMany: jest.fn().mockResolvedValue(cohortEmp),
          andWhere: jest.fn().mockReturnThis(),
        } as any);

        const result = await service.findCohortEmpByGroup('Group 1');

        expect(result).not.toBeNull();
        expect(cohortRepository.createQueryBuilder).toHaveBeenCalledWith(
          'STG_Cohort',
        );
      });
    });
  });

  describe('findEmployeeCohort', () => {
    it('should return employee cohorts by cohort id', async () => {
      const cohortEntity = new CohortEntity();
      jest.spyOn(cohortRepository, 'find').mockResolvedValue([cohortEntity]);

      const result = await service.findEmployeeCohort(1);

      expect(result).toEqual([cohortEntity]);
      expect(cohortRepository.find).toHaveBeenCalledWith({
        relations: ['cohortEmps'],
        where: { cohortPk: 1 },
      });
    });
  });

  describe('findByCohortId', () => {
    it('should return a cohort by cohort id', async () => {
      const cohortEntity = new CohortEntity();
      jest.spyOn(cohortRepository, 'findOne').mockResolvedValue(cohortEntity);

      const result = await service.findByCohortId('1');

      expect(result).toEqual(cohortEntity);
      expect(cohortRepository.findOne).toHaveBeenCalledWith({
        where: { cohortId: '1' },
      });
    });
  });

  describe('batchInsertCohorts', () => {
    it('should insert multiple cohorts', async () => {
      const createCohortDtos: Partial<CreateCohortDto>[] = [
        { cohortId: '1', cohortName: 'Cohort 1', cohortGroupId: 1 },
        { cohortId: '2', cohortName: 'Cohort 2', cohortGroupId: 2 },
      ];
      const result = await service.batchInsertCohorts(createCohortDtos);

      expect(result).not.toBeNull();
    });

    it('should throw an HttpException if the cohort batch insert fails', async () => {
      const createCohortDtos: Partial<CreateCohortDto>[] = [
        { cohortId: '1', cohortName: 'Cohort 1', cohortGroupId: 1 },
        { cohortId: '2', cohortName: 'Cohort 2', cohortGroupId: 2 },
      ];
      const error = new Error('Database error');
      jest.spyOn(cohortRepository, 'insert').mockRejectedValue(error);

      await expect(
        service.batchInsertCohorts(createCohortDtos),
      ).rejects.toThrow(
        new HttpException(error.message, HttpStatus.BAD_REQUEST),
      );
    });
  });
});
