import { WebinarService } from './webinar.service';
import { ZoomUserEndpoint, ZoomRegistrantStatusAction } from '../enums/zoom-role.enum';

describe('WebinarService — removeParticipant / approveParticipant', () => {
  let service: WebinarService;

  const mockRepo = {};
  const mockLogger = { log: jest.fn(), error: jest.fn(), warn: jest.fn() };
  const mockConfig = { get: jest.fn() };
  const mockZoomApi = {
    removeUser: jest.fn(),
    updateWebinarRegistrantStatus: jest.fn(),
    fetchRegistrants: jest.fn(),
    addRegistrant: jest.fn(),
    addPanelist: jest.fn(),
    fetchPanelists: jest.fn().mockResolvedValue([]),
  };

  const session = {
    id: 1392,
    onlineSession: { id: 555, externalId: 'ext-555' },
  } as any;
  const contact = { firstName: 'Seeker', lastName: 'One', email: 'seeker@x.com', name: 'Seeker One' };

  beforeEach(() => {
    jest.clearAllMocks();
    service = new WebinarService(mockRepo as any, mockZoomApi as any, mockConfig as any, mockLogger as any);
  });

  /** `cancelInsteadOfDeleteRegistrant` reads a code-level constant, not env — spy on it directly to exercise the hard-delete branch. */
  function setHardDeleteMode(): void {
    jest.spyOn(service as any, 'cancelInsteadOfDeleteRegistrant').mockReturnValue(false);
  }

  /**
   * `manualApprovalEnabled` reads a code-level constant whose real value can
   * change independently of these tests — spy it explicitly so every test is
   * deterministic regardless of what ZOOM_MANUAL_APPROVAL_REGISTRANTS is set to.
   */
  function setManualApproval(enabled: boolean): void {
    jest.spyOn(service as any, 'manualApprovalEnabled').mockReturnValue(enabled);
  }

  describe('removeParticipant', () => {
    it('cancels a registrant by default instead of deleting it', async () => {
      const extension = { externalRegistrantId: 'reg-1', isPanelist: false } as any;

      await service.removeParticipant(session, extension, contact);

      expect(mockZoomApi.updateWebinarRegistrantStatus).toHaveBeenCalledWith(
        'ext-555',
        ZoomRegistrantStatusAction.CANCEL,
        { id: 'reg-1', email: 'seeker@x.com' },
      );
      expect(mockZoomApi.removeUser).not.toHaveBeenCalled();
    });

    it('always hard-deletes a panelist — Zoom has no status concept for panelists', async () => {
      const extension = { externalRegistrantId: 'panelist-1', isPanelist: true } as any;

      await service.removeParticipant(session, extension, contact);

      expect(mockZoomApi.removeUser).toHaveBeenCalledWith('ext-555', ZoomUserEndpoint.PANELISTS, 'panelist-1');
      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
    });

    it('falls back to a hard delete when ZOOM_CANCEL_INSTEAD_OF_DELETE_REGISTRANT is off', async () => {
      setHardDeleteMode();
      const extension = { externalRegistrantId: 'reg-1', isPanelist: false } as any;

      await service.removeParticipant(session, extension, contact);

      expect(mockZoomApi.removeUser).toHaveBeenCalledWith('ext-555', ZoomUserEndpoint.REGISTRANTS, 'reg-1');
      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
    });

    it('no-ops when the extension holds no registrant id', async () => {
      const extension = { externalRegistrantId: null, isPanelist: false } as any;

      await service.removeParticipant(session, extension, contact);

      expect(mockZoomApi.removeUser).not.toHaveBeenCalled();
      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
    });
  });

  describe('approveParticipant', () => {
    it('re-approves the existing registrant and returns its current join link', async () => {
      const extension = { externalRegistrantId: 'reg-1', isPanelist: false } as any;
      mockZoomApi.fetchRegistrants.mockResolvedValue([{ id: 'reg-1', join_url: 'https://zoom/j/restored' }]);

      const result = await service.approveParticipant(session, extension, contact);

      expect(mockZoomApi.updateWebinarRegistrantStatus).toHaveBeenCalledWith(
        'ext-555',
        ZoomRegistrantStatusAction.APPROVE,
        { id: 'reg-1', email: 'seeker@x.com' },
      );
      expect(result).toEqual({ joinUrl: 'https://zoom/j/restored', zoomRegistrantId: 'reg-1', isPanelist: false });
      expect(mockZoomApi.addRegistrant).not.toHaveBeenCalled();
    });

    it('registers fresh instead of re-approving when in hard-delete mode', async () => {
      setHardDeleteMode();
      setManualApproval(false);
      const extension = { externalRegistrantId: 'reg-1', isPanelist: false } as any;
      mockZoomApi.addRegistrant.mockResolvedValue({ join_url: 'https://zoom/j/new', registrant_id: 'reg-2' });

      const result = await service.approveParticipant(session, extension, contact);

      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
      expect(mockZoomApi.addRegistrant).toHaveBeenCalled();
      expect(result).toEqual({ joinUrl: 'https://zoom/j/new', zoomRegistrantId: 'reg-2', isPanelist: false });
    });

    it('auto-approves the freshly registered fallback when manual approval is enabled, then re-fetches its join_url', async () => {
      setHardDeleteMode();
      setManualApproval(true);
      const extension = { externalRegistrantId: 'reg-1', isPanelist: false } as any;
      // While pending, Zoom's add-registrant response carries no usable join_url.
      mockZoomApi.addRegistrant.mockResolvedValue({ join_url: null, registrant_id: 'reg-2' });
      mockZoomApi.fetchRegistrants.mockResolvedValue([{ id: 'reg-2', join_url: 'https://zoom/j/approved' }]);

      const result = await service.approveParticipant(session, extension, contact);

      expect(mockZoomApi.addRegistrant).toHaveBeenCalled();
      expect(mockZoomApi.updateWebinarRegistrantStatus).toHaveBeenCalledWith(
        'ext-555',
        ZoomRegistrantStatusAction.APPROVE,
        { id: 'reg-2', email: 'seeker@x.com' },
      );
      expect(mockZoomApi.fetchRegistrants).toHaveBeenCalledWith('ext-555');
      expect(result).toEqual({ joinUrl: 'https://zoom/j/approved', zoomRegistrantId: 'reg-2', isPanelist: false });
    });

    it('registers fresh instead of re-approving a panelist', async () => {
      const extension = { externalRegistrantId: 'panelist-1', isPanelist: true } as any;
      mockZoomApi.addRegistrant.mockResolvedValue({ join_url: 'https://zoom/j/new', registrant_id: 'reg-2' });

      await service.approveParticipant(session, extension, contact);

      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
    });

    it('registers fresh when there is no prior registrant id', async () => {
      setManualApproval(false);
      const extension = { externalRegistrantId: null, isPanelist: false } as any;
      mockZoomApi.addRegistrant.mockResolvedValue({ join_url: 'https://zoom/j/new', registrant_id: 'reg-2' });

      await service.approveParticipant(session, extension, contact);

      expect(mockZoomApi.updateWebinarRegistrantStatus).not.toHaveBeenCalled();
      expect(mockZoomApi.addRegistrant).toHaveBeenCalled();
    });
  });

  describe('buildSessionTitle', () => {
    it('prefixes the session number onto an explicitly supplied title', () => {
      const s = { displayOrder: 3, name: 'Session Name', program: { name: 'Program X' } } as any;
      expect((service as any).buildSessionTitle(s, 'Custom Title')).toBe('Session - 3 Custom Title');
    });

    it('falls back to the program name when no explicit title is given', () => {
      const s = { displayOrder: 1, name: 'Session Name', program: { name: 'Program X' } } as any;
      expect((service as any).buildSessionTitle(s)).toBe('Session - 1 Program X');
    });

    it('falls back to the session name when there is no program relation', () => {
      const s = { displayOrder: 2, name: 'Session Name', program: null } as any;
      expect((service as any).buildSessionTitle(s)).toBe('Session - 2 Session Name');
    });

    it('uses "{program name} - Session {N}" for PT_TAT programs, ignoring any explicit title', () => {
      const s = {
        displayOrder: 1,
        name: 'Session Name',
        program: { name: 'TAT Online', type: { key: 'PT_TAT' } },
      } as any;
      expect((service as any).buildSessionTitle(s, 'Custom Title')).toBe('TAT Online - Session 1');
    });
  });
});
