import {
  Controller,
  Get,
  Post,
  Body,
  Query,
  HttpStatus,
  UseGuards,
  ParseIntPipe,
  Req,
  Res,
  ValidationPipe,
} from '@nestjs/common'
import {
  ApiBearerAuth,
  ApiBody,
  ApiOperation,
  ApiQuery,
  ApiResponse,
  ApiSecurity,
  ApiTags,
} from '@nestjs/swagger'
import { Response } from 'express'
import { CombinedAuthGuard } from 'src/auth/combined-auth.guard'
import { RolesGuard } from 'src/common/guards/roles.guard'
import { Roles } from 'src/common/decorators/roles.decorator'
import { ROLE_VALUES } from 'src/common/constants/strings-constants'
import { AppLoggerService } from 'src/common/services/logger.service'
import { ResponseService } from 'src/common/response-handling/response-handler'
import { handleControllerError } from 'src/common/utils/controller-response-handling'
import { ReportsService } from './reports.service'
import { GenerateReportDto } from './dto/generate-report.dto'
import { GenerateSessionAttendanceReportDto } from './dto/generate-session-attendance-report.dto'
import { DownloadZoomLinksDto } from './dto/download-zoom-links.dto'

@ApiTags('Reports')
@ApiBearerAuth('Authorization')
@ApiSecurity('userIdAuth')
@ApiSecurity('activeRoleAuth')
@UseGuards(CombinedAuthGuard, RolesGuard)
@Roles(
  ROLE_VALUES.ADMIN,
  ROLE_VALUES.VIEWER,
  ROLE_VALUES.MAHATRIA,
  ROLE_VALUES.RM,
  ROLE_VALUES.FINANCE_MANAGER,
  ROLE_VALUES.RELATIONAL_MANAGER,
  ROLE_VALUES.COORDINATOR,
  ROLE_VALUES.OPERATIONAL_MANAGER,
  ROLE_VALUES.RM_SUPPORT,
)
@Controller('reports')
export class ReportsController {
  constructor(
    private readonly reportsService: ReportsService,
    private readonly responseService: ResponseService,
    private readonly logger: AppLoggerService,
  ) {}

  @Get('feature-flags')
  @ApiOperation({
    summary: 'Get report-download feature flags for a program',
    description: `Returns which report-download features are enabled for this program, so the UI can show/hide the corresponding buttons/menu items. Flags are resolved through the program's TYPE (HDB/MSD/TAT/...), not the individual program — every program of the same type shares the same toggle. One flag per report/purpose, each independent of the others: enableZoomLinksReport, enableEligibleRegistrationsReport, enableSingleSessionAttendanceReport (single-session-attendance), enableSessionDetailReport (session-report), enableLateComerReport (late-comer-report), enableSessionDropoffsReport (session-dropoffs), enableAttendeesLiveReport (general-attendees), enableGeneralSessionReport (general-session-report), enableGeneralLateComerReport (general-late-comer-report), enableGeneralSessionDropoffsReport (general-session-dropoffs) — PLUS enableSingleSessionReport, which is unrelated to the 4 single-session purpose flags above and exclusively gates the Sessions list screen's own "download session report" affordance (GET /online-session's isSessionReportEnabled).`,
  })
  @ApiQuery({ name: 'programId', required: true, type: Number, description: 'ID of the program to fetch feature flags for' })
  @ApiResponse({
    status: 200,
    description: 'Report-download feature flags for this program',
    schema: {
      example: {
        success: true,
        data: {
          enableZoomLinksReport: true,
          enableEligibleRegistrationsReport: false,
          enableSingleSessionReport: true,
          enableSingleSessionAttendanceReport: true,
          enableSessionDetailReport: false,
          enableLateComerReport: true,
          enableSessionDropoffsReport: false,
          enableAttendeesLiveReport: false,
          enableGeneralSessionReport: true,
          enableGeneralLateComerReport: false,
          enableGeneralSessionDropoffsReport: false,
        },
        error: null,
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Invalid programId' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found' })
  async getReportFeatureFlags(
    @Query('programId', ParseIntPipe) programId: number,
    @Res() res: Response,
  ): Promise<void> {
    try {
      const data = await this.reportsService.getReportFeatureFlags(programId)
      await this.responseService.success(res, 'Report feature flags fetched successfully', data, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }

  @Get('fields')
  @ApiOperation({
    summary: 'Get available report fields for a program',
    description: 'Returns the list of fields available for custom report generation based on the program configuration. Fields are filtered by program flags (e.g. HDB/MSD type, residential, grouped, waitlist).',
  })
  @ApiQuery({ name: 'programId', required: true, type: Number, description: 'ID of the program to fetch fields for' })
  @ApiResponse({
    status: 200,
    description: 'List of available report fields',
    schema: {
      example: {
        success: true,
        data: [
          { key: 'regId', label: 'Registration ID', group: 'Registration', order: 1 },
          { key: 'fullName', label: 'Full Name', group: 'Registration', order: 2 },
          { key: 'email', label: 'Email', group: 'Registration', order: 3 },
        ],
        error: null,
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Invalid programId' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found' })
  async getFields(
    @Query('programId', ParseIntPipe) programId: number,
    @Res() res: Response,
  ): Promise<void> {
    try {
      const data = await this.reportsService.getAvailableFields(programId)
      await this.responseService.success(res, 'Report fields fetched successfully', data, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }

  @Post('generate')
  @ApiOperation({
    summary: 'Generate a custom report',
    description: `Generates a report for the selected fields and filters.
- **format: json** — returns \`{ data, tableHeaders, total }\` in the response body.
- **format: excel** — uploads the report to S3 and returns a signed \`downloadUrl\`.

Use \`GET /reports/fields?programId=<id>\` first to get the valid field keys for a program.`,
  })
  @ApiBody({ type: GenerateReportDto })
  @ApiResponse({
    status: 200,
    description: 'Report generated (JSON format)',
    schema: {
      example: {
        success: true,
        data: {
          tableHeaders: [
            { key: 'regId', alias: 'regId', label: 'Registration ID', order: 1, type: 'string', sortable: false, filterable: false },
            { key: 'fullName', alias: 'fullName', label: 'Full Name', order: 2, type: 'string', sortable: false, filterable: false },
          ],
          data: [
            { regId: 'REG-001', fullName: 'John Doe' },
          ],
          total: 1,
        },
        error: null,
      },
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Report generated (Excel format) — returns a signed S3 download URL',
    schema: {
      example: {
        success: true,
        data: {
          downloadUrl: 'https://s3.amazonaws.com/bucket/exports/excel/report.xlsx?X-Amz-Signature=...',
        },
        error: null,
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Invalid fields or filters' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found (when programId filter is provided)' })
  async generate(
    @Body(new ValidationPipe({ transform: true, whitelist: true })) dto: GenerateReportDto,
    @Req() req: any,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.log('Generate report request', dto)
    try {
      const user = req.user
      const result = await this.reportsService.generateReport(dto, {
        userRoles: user?.roles || [],
        userId: user?.id ?? null,
      })

      if (dto.format === 'excel') {
        const fileName = dto.fileName ?? 'report'
        const downloadUrl = await this.reportsService.buildExcel(result.data, dto.fields, fileName)
        await this.responseService.success(res, 'Report generated successfully', { downloadUrl }, HttpStatus.OK)
        return
      }

      await this.responseService.success(res, 'Report generated successfully', result, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }

  @Get('session-attendance/fields')
  @ApiOperation({
    summary: 'Get available session attendance report fields for a program',
    description: 'Returns the list of fields available for the session-wise attendance report.',
  })
  @ApiQuery({ name: 'programId', required: true, type: Number, description: 'ID of the program to fetch fields for' })
  @ApiResponse({ status: 200, description: 'List of available session attendance report fields' })
  @ApiResponse({ status: 400, description: 'Invalid programId' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found' })
  async getSessionAttendanceFields(
    @Query('programId', ParseIntPipe) programId: number,
    @Res() res: Response,
  ): Promise<void> {
    try {
      const data = await this.reportsService.getAvailableSessionAttendanceFields(programId)
      await this.responseService.success(res, 'Report fields fetched successfully', data, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }

  @Post('session-attendance/generate')
  @ApiOperation({
    summary: 'Generate the session-wise attendance report',
    description: `One row per registrant per session for a program (optionally narrowed to a single session via \`filters.sessionId\`).
- **format: json** — returns \`{ data, tableHeaders, total }\` in the response body.
- **format: excel** — uploads the report to S3 and returns a signed \`downloadUrl\`.
- Pass either \`fields\` (ad-hoc field selection — use \`GET /reports/session-attendance/fields?programId=<id>\` first to get the valid keys) or \`purpose\` (a hardcoded field set for a known report type) — not both. \`purpose: "single-session-attendance"\` expects \`filters.sessionId\`; \`purpose: "all-sessions-attendance"\` leaves \`filters.sessionId\` unset to cover every session of the program in one call — attendance columns come back \`null\` for any session that hasn't finished yet.
- \`purpose: "eligible-registrations-attendance"\` — the Eligible Registrations screen's "download" report. Unlike the other two purposes, this returns **one row per registrant** (not per registrant × session), with the per-session attendance pivoted into dynamic \`Session 1\`..\`Session N\` columns (N = the program's session count) and an \`Eligible for Final Session\` column: \`Absent\` if the registrant missed any conducted session, \`Eligible\` otherwise. \`filters.sessionId\`/\`filters.attended\` are ignored for this purpose.
- \`purpose: "session-report"\` — every attendance-family column merged into one row per registrant × session (single session via \`filters.sessionId\`), including \`duration\`/\`durationSeconds\`, \`lateBy\`/\`lateBySeconds\`, \`dropoffCount\`, \`joinCount\`, \`lastDropoffAt\`.
- \`purpose: "late-comer-report"\` — one row per registrant × session focused on join timing: \`joinedTime\` and \`lateBy\`/\`lateBySeconds\` (exact minutes/seconds past the scheduled start; \`null\` for on-time or never-joined registrants — not filtered out).
- \`purpose: "session-dropoffs"\` — single-session drop-off report; **requires** \`filters.sessionId\` (400 if omitted). One row per registrant who left the session at least once, with \`joinCount\`, \`dropoffCount\`, and every \`leave_reason\` they triggered, read directly from the raw Zoom event log.
- \`purpose: "general-attendees"\` — General Attendees screen "download" report; **requires** \`filters.sessionId\` (400 if omitted). One row per shared/common-link attendee (no registration of their own), with \`fullName\`/\`email\`/\`mobile\` resolved the same way the live screen does (a matched staff/role or placeholder link's own captured details first, then Zoom's own reported name/email), plus \`attendance\`, \`joinedAt\`, \`noOfDevices\`, \`dropoffCount\`, \`durationSeconds\`, and \`sourceTag\` (the matched link's role, or \`Unidentified\`).
- \`purpose: "general-session-report"\` — General Attendees screen counterpart to \`"session-report"\`; **requires** \`filters.sessionId\`. Same rows/identity resolution as \`"general-attendees"\`, plus \`lateBy\`/\`lateBySeconds\`, \`joinCount\`, \`lastDropoffAt\`.
- \`purpose: "general-late-comer-report"\` — General Attendees screen counterpart to \`"late-comer-report"\`; **requires** \`filters.sessionId\`. Join-timing only: \`fullName\`/\`email\`/\`mobile\`/\`sourceTag\`, \`joinedAt\`, \`lateBy\`/\`lateBySeconds\`.
- \`purpose: "general-session-dropoffs"\` — General Attendees screen counterpart to \`"session-dropoffs"\`; **requires** \`filters.sessionId\`. One row per shared/common-link attendee who left this session at least once, with \`joinCount\`, \`dropoffCount\`, and every \`leave_reason\` they triggered, read directly from the raw Zoom event log (scoped to \`+gen\`-tagged emails).`,
  })
  @ApiBody({ type: GenerateSessionAttendanceReportDto })
  @ApiResponse({ status: 200, description: 'Session attendance report generated' })
  @ApiResponse({ status: 400, description: 'Invalid fields, purpose or filters' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found' })
  async generateSessionAttendance(
    @Body(new ValidationPipe({ transform: true, whitelist: true })) dto: GenerateSessionAttendanceReportDto,
    @Req() req: any,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.log('Generate session attendance report request', dto)
    try {
      const user = req.user
      const result = await this.reportsService.generateSessionAttendanceReport(dto, {
        userRoles: user?.roles || [],
        userId: user?.id ?? null,
      })

      if (dto.format === 'excel') {
        const fileName = dto.fileName ?? 'session_attendance_report'
        const fields = result.tableHeaders.map(header => header.key)
        const downloadUrl = await this.reportsService.buildSessionAttendanceExcel(result.data, fields, fileName, result.fieldDefs)
        await this.responseService.success(res, 'Report generated successfully', { downloadUrl }, HttpStatus.OK)
        return
      }

      await this.responseService.success(res, 'Report generated successfully', result, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }

  @Get('zoom-links/download')
  @ApiOperation({
    summary: 'Download all generated Zoom join links for a program',
    description: 'Downloads every generated Zoom join link across all sessions of a program, with registrant basic details (name, email, phone), RM and session details. Always includes every field — there is no field selection. Uploads to S3 and returns a signed downloadUrl.',
  })
  @ApiQuery({ name: 'programId', required: true, type: Number, description: 'ID of the program to download Zoom links for' })
  @ApiQuery({ name: 'fileName', required: false, type: String, description: 'Custom file name for the excel download (without extension)' })
  @ApiResponse({
    status: 200,
    description: 'Zoom links excel generated — returns a signed S3 download URL',
    schema: {
      example: {
        success: true,
        data: { downloadUrl: 'https://s3.amazonaws.com/bucket/exports/excel/zoom-links-program-123.xlsx?X-Amz-Signature=...' },
        error: null,
      },
    },
  })
  @ApiResponse({ status: 400, description: 'No Zoom links found for this program' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Program not found' })
  async downloadZoomLinks(
    @Query(new ValidationPipe({ transform: true, whitelist: true })) dto: DownloadZoomLinksDto,
    @Req() req: any,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.log('Download Zoom links request', dto)
    try {
      const user = req.user
      const downloadUrl = await this.reportsService.downloadZoomLinks(dto, {
        userRoles: user?.roles || [],
        userId: user?.id ?? null,
      })
      await this.responseService.success(res, 'Zoom links generated successfully', { downloadUrl }, HttpStatus.OK)
    } catch (error) {
      await handleControllerError(res, error)
    }
  }
}
