import { ZoomAnalyticsLivePollScheduler } from './zoom-analytics-live-poll.scheduler';
import { ZoomAnalyticsResourceType } from 'src/common/enum/zoom-analytics-resource-type.enum';

describe('ZoomAnalyticsLivePollScheduler', () => {
  const config = {
    isEnabled: jest.fn(),
    isLivePollEnabled: jest.fn(),
  };
  const provider = { correctLiveState: jest.fn() };
  const meetingProvider = { correctLiveState: jest.fn() };
  const registry = { resolve: jest.fn().mockReturnValue(provider) };
  const webinarRepository = { findById: jest.fn() };
  const sessionSummaryRepository = { findLive: jest.fn() };
  const dashboardApiClient = { fetchLiveParticipants: jest.fn() };
  const logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn() };

  let scheduler: ZoomAnalyticsLivePollScheduler;

  beforeEach(() => {
    jest.clearAllMocks();
    config.isEnabled.mockReturnValue(true);
    config.isLivePollEnabled.mockReturnValue(true);
    registry.resolve.mockReturnValue(provider);
    scheduler = new ZoomAnalyticsLivePollScheduler(
      config as any,
      registry as any,
      webinarRepository as any,
      sessionSummaryRepository as any,
      dashboardApiClient as any,
      logger as any,
    );
  });

  it('does nothing when the master flag is off, even if the live-poll flag is on', async () => {
    config.isEnabled.mockReturnValue(false);
    await scheduler.run();
    expect(sessionSummaryRepository.findLive).not.toHaveBeenCalled();
  });

  it('does nothing when the live-poll flag is off', async () => {
    config.isLivePollEnabled.mockReturnValue(false);
    await scheduler.run();
    expect(sessionSummaryRepository.findLive).not.toHaveBeenCalled();
  });

  it('no-ops cleanly when the Dashboard API is unavailable (returns null)', async () => {
    sessionSummaryRepository.findLive.mockResolvedValue([
      { sessionId: 7, resourceType: ZoomAnalyticsResourceType.WEBINAR },
    ]);
    webinarRepository.findById.mockResolvedValue({ onlineSession: { externalId: 'ext-1' } });
    dashboardApiClient.fetchLiveParticipants.mockResolvedValue(null);

    await scheduler.run();

    expect(provider.correctLiveState).not.toHaveBeenCalled();
  });

  it('self-heals live state using the dashboard-reported present emails and names', async () => {
    const session = { id: 7, onlineSession: { externalId: 'ext-1' } };
    sessionSummaryRepository.findLive.mockResolvedValue([
      { sessionId: 7, resourceType: ZoomAnalyticsResourceType.WEBINAR },
    ]);
    webinarRepository.findById.mockResolvedValue(session);
    dashboardApiClient.fetchLiveParticipants.mockResolvedValue([
      { user_email: 'A@X.com', user_name: 'Alice' },
      { email: 'b@x.com' },
    ]);

    await scheduler.run();

    expect(registry.resolve).toHaveBeenCalledWith(ZoomAnalyticsResourceType.WEBINAR);
    expect(provider.correctLiveState).toHaveBeenCalledWith(
      session,
      new Map([
        ['a@x.com', 'Alice'],
        ['b@x.com', null],
      ]),
    );
  });

  it('resolves the provider from each row\'s own resourceType — a webinar and a meeting session in the same pass go to different providers', async () => {
    const webinarSession = { id: 7, onlineSession: { externalId: 'ext-1' } };
    const meetingSession = { id: 8, onlineSession: { externalId: 'ext-2' } };
    sessionSummaryRepository.findLive.mockResolvedValue([
      { sessionId: 7, resourceType: ZoomAnalyticsResourceType.WEBINAR },
      { sessionId: 8, resourceType: ZoomAnalyticsResourceType.MEETING },
    ]);
    webinarRepository.findById.mockResolvedValueOnce(webinarSession).mockResolvedValueOnce(meetingSession);
    registry.resolve.mockImplementation((resourceType) =>
      resourceType === ZoomAnalyticsResourceType.MEETING ? meetingProvider : provider,
    );
    dashboardApiClient.fetchLiveParticipants.mockResolvedValue([]);

    await scheduler.run();

    expect(registry.resolve).toHaveBeenCalledWith(ZoomAnalyticsResourceType.WEBINAR);
    expect(registry.resolve).toHaveBeenCalledWith(ZoomAnalyticsResourceType.MEETING);
    expect(provider.correctLiveState).toHaveBeenCalledWith(webinarSession, new Map());
    expect(meetingProvider.correctLiveState).toHaveBeenCalledWith(meetingSession, new Map());

    // The Dashboard API has separate meeting/webinar endpoints — passing the wrong resourceType
    // silently hits the wrong one for that session (a meeting queried as a webinar 404s).
    expect(dashboardApiClient.fetchLiveParticipants).toHaveBeenCalledWith(
      'ext-1',
      ZoomAnalyticsResourceType.WEBINAR,
    );
    expect(dashboardApiClient.fetchLiveParticipants).toHaveBeenCalledWith(
      'ext-2',
      ZoomAnalyticsResourceType.MEETING,
    );
  });

  it('isolates a per-session failure so one bad session does not stop the rest', async () => {
    sessionSummaryRepository.findLive.mockResolvedValue([
      { sessionId: 7, resourceType: ZoomAnalyticsResourceType.WEBINAR },
      { sessionId: 8, resourceType: ZoomAnalyticsResourceType.WEBINAR },
    ]);
    webinarRepository.findById
      .mockRejectedValueOnce(new Error('db down'))
      .mockResolvedValueOnce({ id: 8, onlineSession: { externalId: 'ext-2' } });
    dashboardApiClient.fetchLiveParticipants.mockResolvedValue([]);

    await scheduler.run();

    expect(logger.error).toHaveBeenCalled();
    expect(provider.correctLiveState).toHaveBeenCalledTimes(1);
  });
});
