import { QueryFailedError } from 'typeorm';
import { ZoomGeneratedRegistrantLinkRepository } from './zoom-generated-registrant-link.repository';
import { GeneratedLinkSourceType } from 'src/common/enum/generated-link-source-type.enum';
import { OnlineSessionRegistrationStatus } from 'src/common/enum/online-session-registration-status.enum';

describe('ZoomGeneratedRegistrantLinkRepository', () => {
  const repo = { find: jest.fn(), softDelete: jest.fn(), save: jest.fn() };
  const logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn() };

  let repository: ZoomGeneratedRegistrantLinkRepository;

  beforeEach(() => {
    jest.clearAllMocks();
    repository = new ZoomGeneratedRegistrantLinkRepository(repo as any, logger as any);
  });

  describe('excludeRegisteredAndClearFailedByUser', () => {
    it('returns REGISTERED user ids to skip and soft-deletes stale FAILED rows so a retry insert will not collide', async () => {
      repo.find.mockResolvedValue([
        { id: 1, userId: 42, status: OnlineSessionRegistrationStatus.REGISTERED },
        { id: 2, userId: 43, status: OnlineSessionRegistrationStatus.FAILED },
      ]);

      const result = await repository.excludeRegisteredAndClearFailedByUser(7, [42, 43]);

      expect(result).toEqual(new Set([42]));
      expect(repo.softDelete).toHaveBeenCalledWith([2]);
    });

    it('does not call softDelete when nothing is stale', async () => {
      repo.find.mockResolvedValue([{ id: 1, userId: 42, status: OnlineSessionRegistrationStatus.REGISTERED }]);

      await repository.excludeRegisteredAndClearFailedByUser(7, [42]);

      expect(repo.softDelete).not.toHaveBeenCalled();
    });

    it('short-circuits without a query for an empty user id list', async () => {
      const result = await repository.excludeRegisteredAndClearFailedByUser(7, []);

      expect(result).toEqual(new Set());
      expect(repo.find).not.toHaveBeenCalled();
    });
  });

  describe('excludeRegisteredAndClearFailedByBatch', () => {
    it('returns REGISTERED sequence numbers to skip and soft-deletes stale FAILED rows', async () => {
      repo.find.mockResolvedValue([
        { id: 1, sequenceNumber: 1, status: OnlineSessionRegistrationStatus.REGISTERED },
        { id: 2, sequenceNumber: 2, status: OnlineSessionRegistrationStatus.FAILED },
      ]);

      const result = await repository.excludeRegisteredAndClearFailedByBatch(7, 'Staff');

      expect(result).toEqual(new Set([1]));
      expect(repo.softDelete).toHaveBeenCalledWith([2]);
      expect(repo.find).toHaveBeenCalledWith(
        expect.objectContaining({
          where: expect.objectContaining({ sourceType: GeneratedLinkSourceType.PLACEHOLDER, batchName: 'Staff' }),
        }),
      );
    });
  });

  describe('saveMany', () => {
    it('logs a unique-constraint violation on the recipient-uniqueness indexes as a quiet warning, not an error', async () => {
      const raceError = Object.assign(new QueryFailedError('INSERT ...', [], new Error('duplicate') as any), {
        code: '23505',
        constraint: 'uq_zoom_generated_link_session_user',
      });
      repo.save.mockRejectedValueOnce(raceError);

      const saved = await repository.saveMany([{ userId: 42 } as any]);

      expect(saved).toEqual([]);
      expect(logger.warn).toHaveBeenCalledWith(
        expect.stringContaining('already registered by a concurrent request'),
        expect.objectContaining({ row: { userId: 42 } }),
      );
      expect(logger.error).not.toHaveBeenCalled();
    });

    it('still logs an unrelated save failure as an error', async () => {
      repo.save.mockRejectedValueOnce(new Error('connection reset'));

      const saved = await repository.saveMany([{ userId: 42 } as any]);

      expect(saved).toEqual([]);
      expect(logger.error).toHaveBeenCalled();
      expect(logger.warn).not.toHaveBeenCalled();
    });
  });
});
