import { Test, TestingModule } from '@nestjs/testing';
import { AnalyticsV1Controller } from './analytics-v1.controller';
import { ZoomAnalyticsFacadeService } from 'src/zoom/services/zoom-analytics-facade.service';
import { ResponseService } from 'src/common/response-handling/response-handler';
import { CombinedAuthGuard } from 'src/auth/combined-auth.guard';
import { RolesGuard } from 'src/common/guards/roles.guard';
import InifniBadRequestException from 'src/common/exceptions/infini-badrequest-exception';
import { ERROR_CODES } from 'src/common/constants/error-string-constants';
import { SessionKpiCategory, SessionKpiFilter } from 'src/common/enum/session-kpi.enum';

jest.mock('src/common/utils/controller-response-handling', () => ({
  handleControllerError: jest.fn((res, error) => res.json({ success: false, error })),
}));

const makeMockRes = () => ({
  json: jest.fn(),
  status: jest.fn().mockReturnThis(),
});

const makeMockReq = (userId = 1, roles: string[] = ['admin']) => ({ user: { id: userId, roles } });

describe('AnalyticsV1Controller', () => {
  let controller: AnalyticsV1Controller;

  const mockFacade = {
    getKpisV1: jest.fn(),
    getAttendeeTableV1: jest.fn(),
    getGeneralAttendeesV1: jest.fn(),
    getLiveStatusV1: jest.fn(),
  };

  const mockResponseService = {
    success: jest.fn((res, _msg, data) => res.json({ success: true, data })),
  };

  beforeEach(async () => {
    jest.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      controllers: [AnalyticsV1Controller],
      providers: [
        { provide: ZoomAnalyticsFacadeService, useValue: mockFacade },
        { provide: ResponseService, useValue: mockResponseService },
      ],
    })
      .overrideGuard(CombinedAuthGuard)
      .useValue({ canActivate: () => true })
      .overrideGuard(RolesGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<AnalyticsV1Controller>(AnalyticsV1Controller);
  });

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

  describe('getKpis', () => {
    it('returns the KPI tiles (with sideFilterSets) for the given session', async () => {
      const res = makeMockRes();
      const kpis = {
        sessionId: 7,
        totalPanelists: 10,
        kpis: [],
        sideFilterSets: {
          baseSets: { rmContact: [], systemAttendance: [], rmAttendance: [], coordinatorAttendance: [], finalAttendance: [], attendanceOutcome: [] },
          contextualFilters: { all: ['rmContact', 'systemAttendance', 'rmAttendance', 'coordinatorAttendance', 'finalAttendance', 'attendanceOutcome'] },
        },
      };
      mockFacade.getKpisV1.mockResolvedValue(kpis);

      await controller.getKpis(7, makeMockReq() as any, res as any);

      expect(mockFacade.getKpisV1).toHaveBeenCalledWith(7, ['admin'], 1);
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), kpis);
    });

    it("forwards the RM caller's active role and id to the facade for scoping", async () => {
      const res = makeMockRes();
      mockFacade.getKpisV1.mockResolvedValue({ sessionId: 7, kpis: [] });

      await controller.getKpis(7, makeMockReq(42, ['relational_manager']) as any, res as any);

      expect(mockFacade.getKpisV1).toHaveBeenCalledWith(7, ['relational_manager'], 42);
    });
  });

  describe('getLive', () => {
    it('forwards the query params to the facade and returns the paginated attendee table plus the live snapshot and currently active/inactive kpi tiles', async () => {
      const res = makeMockRes();
      const query = { page: 1, limit: 20, filters: { kpiCategory: SessionKpiCategory.ATTENDANCE, kpiFilter: SessionKpiFilter.ACTIVE } };
      const live = {
        data: [],
        total: 3,
        page: 1,
        limit: 20,
        tableHeaders: [],
        appliedKpi: { kpiCategory: SessionKpiCategory.ATTENDANCE, kpiFilter: SessionKpiFilter.ACTIVE },
        sessionId: 7,
        isLive: true,
        currentlyJoined: 4,
        totalJoinedSoFar: 6,
        rejoinsSoFar: 1,
        lastEventAt: new Date('2026-01-01T00:00:00Z'),
        kpis: [
          { label: 'Currently active', value: 3, kpiCategory: SessionKpiCategory.ATTENDANCE, kpiFilter: SessionKpiFilter.ACTIVE },
          { label: 'Currently inactive', value: 7, kpiCategory: SessionKpiCategory.ATTENDANCE, kpiFilter: SessionKpiFilter.INACTIVE },
        ],
      };
      mockFacade.getLiveStatusV1.mockResolvedValue(live);

      await controller.getLive(7, query as any, makeMockReq() as any, res as any);

      expect(mockFacade.getLiveStatusV1).toHaveBeenCalledWith(7, query, ['admin'], 1);
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), live);
    });

    it("forwards the RM caller's active role and id to the facade for scoping", async () => {
      const res = makeMockRes();
      const query = { page: 1, limit: 20 };
      mockFacade.getLiveStatusV1.mockResolvedValue({ sessionId: 7, kpis: [] });

      await controller.getLive(7, query as any, makeMockReq(42, ['relational_manager']) as any, res as any);

      expect(mockFacade.getLiveStatusV1).toHaveBeenCalledWith(7, query, ['relational_manager'], 42);
    });
  });

  describe('getAttendees', () => {
    it('forwards the bundled filters (kpiCategory/kpiFilter/rmContact) and sort query params to the facade', async () => {
      const res = makeMockRes();
      const query = {
        page: 1,
        limit: 20,
        filters: { kpiCategory: SessionKpiCategory.ATTENDANCE, kpiFilter: SessionKpiFilter.DROPPED, rmContact: 55 },
        sortKey: 'joinedAt',
        sortOrder: 'DESC',
      };
      const rows = { data: [], total: 0, page: 1, limit: 20, tableHeaders: [], appliedKpi: null };
      mockFacade.getAttendeeTableV1.mockResolvedValue(rows);

      await controller.getAttendees(7, query as any, makeMockReq() as any, res as any);

      expect(mockFacade.getAttendeeTableV1).toHaveBeenCalledWith(7, query, ['admin'], 1);
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), rows);
    });

    it('calls handleControllerError when filters.kpiCategory/filters.kpiFilter are passed unpaired', async () => {
      const res = makeMockRes();
      const query = { page: 1, limit: 20, filters: { kpiCategory: SessionKpiCategory.ATTENDANCE } };
      mockFacade.getAttendeeTableV1.mockRejectedValue(
        new InifniBadRequestException(ERROR_CODES.ZOOM_ANALYTICS_INVALID_KPI_FILTER),
      );

      await controller.getAttendees(7, query as any, makeMockReq() as any, res as any);

      expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ success: false }));
    });

    it('calls handleControllerError when an RM caller passes a filters.rmContact outside their own scope', async () => {
      const res = makeMockRes();
      const query = { page: 1, limit: 20, filters: { rmContact: 999 } };
      mockFacade.getAttendeeTableV1.mockRejectedValue(
        new InifniBadRequestException(ERROR_CODES.ZOOM_ANALYTICS_RM_SCOPE_CONFLICT, null, null, '999'),
      );

      await controller.getAttendees(7, query as any, makeMockReq(42, ['relational_manager']) as any, res as any);

      expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ success: false }));
    });
  });

  describe('getGeneralAttendees', () => {
    it('forwards page/limit/search/sort/attendanceOutcome to the facade and returns its result', async () => {
      const res = makeMockRes();
      const query = { page: 1, limit: 20, search: 'walkin', sortKey: 'joinedAt', sortOrder: 'DESC' };
      const rows = { data: [], total: 0, page: 1, limit: 20, tableHeaders: [], appliedKpi: null };
      mockFacade.getGeneralAttendeesV1.mockResolvedValue(rows);

      await controller.getGeneralAttendees(7, query as any, makeMockReq() as any, res as any);

      expect(mockFacade.getGeneralAttendeesV1).toHaveBeenCalledWith(7, query, ['admin'], 1);
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), rows);
    });

    it("forwards the RM caller's active role and id to the facade for scoping", async () => {
      const res = makeMockRes();
      mockFacade.getGeneralAttendeesV1.mockResolvedValue({ data: [], total: 0, page: 1, limit: 20 });

      await controller.getGeneralAttendees(
        7,
        { page: 1, limit: 20 } as any,
        makeMockReq(42, ['relational_manager']) as any,
        res as any,
      );

      expect(mockFacade.getGeneralAttendeesV1).toHaveBeenCalledWith(7, { page: 1, limit: 20 }, ['relational_manager'], 42);
    });
  });
});
