import { Test, TestingModule } from '@nestjs/testing';
import { ReportsController } from './reports.controller';
import { ReportsService } from './reports.service';
import { ResponseService } from 'src/common/response-handling/response-handler';
import { AppLoggerService } from 'src/common/services/logger.service';
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';

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 = (roles: string[] = ['admin'], id = 1) => ({
  user: { roles, id },
});

describe('ReportsController — session attendance endpoints', () => {
  let controller: ReportsController;

  const mockReportsService = {
    getAvailableSessionAttendanceFields: jest.fn(),
    generateSessionAttendanceReport: jest.fn(),
    buildSessionAttendanceExcel: jest.fn(),
  };
  const mockResponseService = {
    success: jest.fn((res, _msg, data) => res.json({ success: true, data })),
  };
  const mockLogger = { log: jest.fn() };

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

    const module: TestingModule = await Test.createTestingModule({
      controllers: [ReportsController],
      providers: [
        { provide: ReportsService, useValue: mockReportsService },
        { provide: ResponseService, useValue: mockResponseService },
        { provide: AppLoggerService, useValue: mockLogger },
      ],
    })
      .overrideGuard(CombinedAuthGuard)
      .useValue({ canActivate: () => true })
      .overrideGuard(RolesGuard)
      .useValue({ canActivate: () => true })
      .compile();

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

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

  describe('getSessionAttendanceFields', () => {
    it('returns the field list for a program', async () => {
      const res = makeMockRes();
      const fields = [{ key: 'sessionName', label: 'Session Name', group: 'Session', order: 1 }];
      mockReportsService.getAvailableSessionAttendanceFields.mockResolvedValue(fields);

      await controller.getSessionAttendanceFields(1, res as any);

      expect(mockReportsService.getAvailableSessionAttendanceFields).toHaveBeenCalledWith(1);
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), fields, 200);
    });

    it('delegates to handleControllerError when the program is missing', async () => {
      const res = makeMockRes();
      mockReportsService.getAvailableSessionAttendanceFields.mockRejectedValue(
        new InifniBadRequestException(ERROR_CODES.PROGRAM_NOT_FOUND),
      );

      await controller.getSessionAttendanceFields(999, res as any);

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

  describe('generateSessionAttendance', () => {
    it('returns JSON report data when format is omitted', async () => {
      const res = makeMockRes();
      const result = { data: [{ sessionName: 'Session 1' }], tableHeaders: [], total: 1 };
      mockReportsService.generateSessionAttendanceReport.mockResolvedValue(result);
      const dto = { fields: ['sessionName'], filters: { programId: 1 } };

      await controller.generateSessionAttendance(dto as any, makeMockReq() as any, res as any);

      expect(mockReportsService.generateSessionAttendanceReport).toHaveBeenCalledWith(
        dto,
        { userRoles: ['admin'], userId: 1 },
      );
      expect(mockReportsService.buildSessionAttendanceExcel).not.toHaveBeenCalled();
      expect(mockResponseService.success).toHaveBeenCalledWith(res, expect.any(String), result, 200);
    });

    it('builds and returns a signed Excel download URL when format is excel', async () => {
      const res = makeMockRes();
      const result = {
        data: [{ sessionName: 'Session 1' }],
        tableHeaders: [{ key: 'sessionName', alias: 'sessionName', label: 'Session Name', order: 1, type: 'string', sortable: false, filterable: false }],
        total: 1,
      };
      mockReportsService.generateSessionAttendanceReport.mockResolvedValue(result);
      mockReportsService.buildSessionAttendanceExcel.mockResolvedValue('https://signed-url');
      const dto = { fields: ['sessionName'], filters: { programId: 1 }, format: 'excel' as const };

      await controller.generateSessionAttendance(dto as any, makeMockReq() as any, res as any);

      expect(mockReportsService.buildSessionAttendanceExcel).toHaveBeenCalledWith(
        result.data,
        ['sessionName'],
        'session_attendance_report',
        undefined,
      );
      expect(mockResponseService.success).toHaveBeenCalledWith(
        res,
        expect.any(String),
        { downloadUrl: 'https://signed-url' },
        200,
      );
    });

    it('builds an Excel download from the resolved purpose field set (fields omitted)', async () => {
      const res = makeMockRes();
      const result = {
        data: [{ sessionName: 'Session 1' }],
        tableHeaders: [{ key: 'sessionName', alias: 'sessionName', label: 'Session Name', order: 1, type: 'string', sortable: false, filterable: false }],
        total: 1,
      };
      mockReportsService.generateSessionAttendanceReport.mockResolvedValue(result);
      mockReportsService.buildSessionAttendanceExcel.mockResolvedValue('https://signed-url');
      const dto = { purpose: 'single-session-attendance' as const, filters: { programId: 1 }, format: 'excel' as const };

      await controller.generateSessionAttendance(dto as any, makeMockReq() as any, res as any);

      expect(mockReportsService.buildSessionAttendanceExcel).toHaveBeenCalledWith(
        result.data,
        ['sessionName'],
        'session_attendance_report',
        undefined,
      );
    });

    it('passes result.fieldDefs through to buildSessionAttendanceExcel for dynamic-column purposes (e.g. eligible-registrations-attendance)', async () => {
      const res = makeMockRes();
      const fieldDefs = {
        session_1: { key: 'session_1', alias: 'session_1', label: 'Session 1', group: 'Attendance', order: 6, format: null, dependsOn: null, requiredFlags: null, conditionalFormat: null },
      };
      const result = {
        data: [{ session_1: 'Attended' }],
        tableHeaders: [{ key: 'session_1', alias: 'session_1', label: 'Session 1', order: 6, type: 'string', sortable: false, filterable: false }],
        total: 1,
        fieldDefs,
      };
      mockReportsService.generateSessionAttendanceReport.mockResolvedValue(result);
      mockReportsService.buildSessionAttendanceExcel.mockResolvedValue('https://signed-url');
      const dto = { purpose: 'eligible-registrations-attendance' as const, filters: { programId: 1 }, format: 'excel' as const };

      await controller.generateSessionAttendance(dto as any, makeMockReq() as any, res as any);

      expect(mockReportsService.buildSessionAttendanceExcel).toHaveBeenCalledWith(
        result.data,
        ['session_1'],
        'session_attendance_report',
        fieldDefs,
      );
    });

    it('delegates to handleControllerError on invalid fields', async () => {
      const res = makeMockRes();
      mockReportsService.generateSessionAttendanceReport.mockRejectedValue(
        new InifniBadRequestException(ERROR_CODES.INVALID_REPORT_FIELDS),
      );
      const dto = { fields: ['unknownField'], filters: { programId: 1 } };

      await controller.generateSessionAttendance(dto as any, makeMockReq() as any, res as any);

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