import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Post,
  Query,
  Req,
  Res,
  UseGuards,
  ValidationPipe,
} from '@nestjs/common';
import { Response } from 'express';
import {
  ApiBearerAuth,
  ApiBody,
  ApiOperation,
  ApiQuery,
  ApiResponse,
  ApiSecurity,
  ApiTags,
} from '@nestjs/swagger';
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 { ResponseService } from 'src/common/response-handling/response-handler';
import { AppLoggerService } from 'src/common/services/logger.service';
import { handleControllerError } from 'src/common/utils/controller-response-handling';
import { extractAndValidateUser } from 'src/common/utils/program-question.util';
import { ROLE_VALUES, SWAGGER_API_RESPONSE } from 'src/common/constants/strings-constants';
import { SessionCommunicationService } from './session-communication.service';
import {
  SendBulkSessionCommunicationDto,
  SendGeneralLinkBulkDto,
  SendGeneralLinkSingleDto,
  SendSingleSessionCommunicationDto,
  SendValueCardBulkDto,
  SendValueCardSingleDto,
  SendCommonInviteBulkDto,
  SendSystemLinksBulkDto,
  SendGeneralLinkValueCardBulkDto,
  SendGeneralLinkValueCardSingleDto,
} from './dto/send-session-communication.dto';

@ApiTags('session-communication')
@Controller('session-communication')
@UseGuards(CombinedAuthGuard, RolesGuard)
@Roles(
  ROLE_VALUES.ADMIN,
  ROLE_VALUES.RELATIONAL_MANAGER,
  ROLE_VALUES.COORDINATOR,
  ROLE_VALUES.OPERATIONAL_MANAGER,
)
@ApiBearerAuth('Authorization')
@ApiSecurity('userIdAuth')
@ApiSecurity('activeRoleAuth')
export class SessionCommunicationController {
  constructor(
    private readonly service: SessionCommunicationService,
    private readonly responseService: ResponseService,
    private readonly logger: AppLoggerService,
  ) {}

  @Post('bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a bulk session communication (Absent / Invite / Value Card)',
    description:
      'Sends the chosen communication to the eligible registrations of an online session. ' +
      'Channels are derived from the purpose (Value Card is email only). ' +
      'ACCEPTED, not completed: the session, the templates and the recipient list are validated ' +
      'in-request (so an unknown session, a missing template or an empty audience still fail ' +
      'here), then the response returns immediately with `accepted: true` and a real `requested` ' +
      'count while the dispatch runs in the background — `enqueued` is all zeros and `skipped` ' +
      "is empty for that reason. Poll the session GET response's `communications` field for the " +
      'real outcome.',
  })
  @ApiBody({ type: SendBulkSessionCommunicationDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendBulkSessionCommunicationDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk session communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      // background: the HTTP caller gets an acceptance; the dispatch runs off-request.
      const data = await this.service.sendBulk(dto, user.id, { background: true });
      // "accepted", not "sent": the dispatch runs in the background — see the operation description.
      await this.responseService.success(res, 'Session communication accepted for sending', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('value-card/bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a bulk Value Card communication (email only)',
    description:
      'Sends the Value Card email to the eligible registrations of an online session, with an ' +
      'admin-supplied description merged into the template and optional file attachments.',
  })
  @ApiBody({ type: SendValueCardBulkDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendValueCardBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendValueCardBulkDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk value card communication request', {
      programId: dto.programId,
      sessionId: dto.sessionId,
      selectionMode: dto.selectionMode,
      attachments: dto.attachmentUrls?.length ?? 0,
    });
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendValueCardBulk(dto, user.id);
      await this.responseService.success(res, 'Value card sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('value-card/single')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send the Value Card to a single registration (email only)',
    description:
      "Top-up send for one registration the session's bulk Value Card run did not reach (a late " +
      'registrant, or an address since corrected). The description and file attachments are read ' +
      "from the session's stored value-card details — NOT from this request — so every recipient " +
      'of a session receives identical content. Requires that the bulk send has already gone out ' +
      'for this session and that those stored details exist; rejects with VALUE_CARD_BULK_NOT_SENT ' +
      'or VALUE_CARD_DETAILS_MISSING otherwise. The registration must also pass the same ' +
      'applicability rule the bulk audience uses.',
  })
  @ApiBody({ type: SendValueCardSingleDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendValueCardSingle(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendValueCardSingleDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received single value card communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendValueCardSingle(dto, user.id);
      await this.responseService.success(res, 'Value card sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('common-invite/bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a bulk Common-Invite communication (email + WhatsApp)',
    description:
      'Notifies the staff who were issued a generated common Zoom link (RM / Admin / Shoba) of ' +
      'the join link, meeting id and passcode. Program-level by default; pass sessionId to scope ' +
      'to one session and use the per-session template. Audience is derived from the generated links. ' +
      'Once-only: rejects with SESSION_COMMUNICATION_ALREADY_TRIGGERED (409) if this scope has ' +
      'already been sent — program-level and per-session are tracked separately.',
  })
  @ApiBody({ type: SendCommonInviteBulkDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendCommonInviteBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendCommonInviteBulkDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk common-invite communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendCommonInviteBulk(dto.programId, user.id, dto.sessionId);
      await this.responseService.success(res, 'Common invite sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('system-links/bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a bulk System-Links communication (email only)',
    description:
      "Notifies the program's ADMIN users of the generated system/placeholder Zoom links as a " +
      'table (name + link). Program-level by default; pass sessionId to scope to one session and ' +
      'use the per-session template. The audience and link list are derived on the server. ' +
      'Once-only: rejects with SESSION_COMMUNICATION_ALREADY_TRIGGERED (409) if this scope has ' +
      'already been sent — program-level and per-session are tracked separately. ' +
      'Rejects with SESSION_COMMUNICATION_NO_SYSTEM_LINKS when no usable system link exists for ' +
      'the program (or the requested session) — the links are the whole message, so an empty ' +
      'table is never sent.',
  })
  @ApiBody({ type: SendSystemLinksBulkDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendSystemLinksBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendSystemLinksBulkDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk system-links communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendSystemLinksBulk(dto.programId, user.id, dto.sessionId);
      await this.responseService.success(res, 'System links sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('general-link/bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary:
      'Send a bulk communication to general-link recipients (Welcome / Invite / Absent / Program completion)',
    description:
      'Sends the chosen communication to everyone who joined via a generated general Zoom link ' +
      '(zoom_generated_registrant_link) and has a real user_id, for the program or just one ' +
      'session when `sessionId` is given, reusing the same WELCOME/INVITE/ABSENT/' +
      'PROGRAM_COMPLETION templates as the seeker flow. PLACEHOLDER rows (anonymous batch slots ' +
      'with no user_id) are never sent to. Status must be REGISTERED, and Absent is always gated ' +
      'to real absentees of the target session. Invite at the final session is NOT gated by ' +
      'prior-session attendance (unlike the seeker rule) — these recipients are staff/admin, not ' +
      'seekers on a structured journey; `occurrence` only picks the template.',
  })
  @ApiBody({ type: SendGeneralLinkBulkDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendGeneralLinkBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendGeneralLinkBulkDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk general-link communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendGeneralLinkBulk(dto, user.id);
      await this.responseService.success(res, 'General-link communication sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('general-link/single')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a single general-link communication (Welcome / Invite / Absent / Program completion)',
    description:
      'Sends the chosen communication to one zoom_generated_registrant_link row, identified by its ' +
      'own id. For Invite/Absent, `occurrence` picks the regular- or final-session template ' +
      '(chosen manually — there is no attendance data to derive it from); omit it for Welcome/ ' +
      'Program completion, which are occurrence-independent.',
  })
  @ApiBody({ type: SendGeneralLinkSingleDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendGeneralLinkSingle(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendGeneralLinkSingleDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received single general-link communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendGeneralLinkSingle(dto, user.id);
      await this.responseService.success(res, 'General-link communication sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('general-link/value-card/bulk')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a bulk general-link ("pre-test") Value Card communication (email only)',
    description:
      'Sends the Value Card email to the general-link recipients of an online session ' +
      '(zoom_generated_registrant_link) who ATTENDED it — post-session material, same rule as the ' +
      'seeker Value Card. Attendance comes from the general-attendee rows of ' +
      'zoom_analytics_attendee_summary, matched on email. Carries an admin-supplied description ' +
      'merged into the template and file attachments. Reuses the seeker VALUE_CARD template, but the description ' +
      'and files are stored separately on the session as pretest_value_card_details — so a seeker ' +
      "value card and a general-link one for the same session never overwrite each other's " +
      'material. This endpoint is the only writer of those stored details.',
  })
  @ApiBody({ type: SendGeneralLinkValueCardBulkDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendGeneralLinkValueCardBulk(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendGeneralLinkValueCardBulkDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received bulk general-link value card communication request', {
      programId: dto.programId,
      sessionId: dto.sessionId,
      attachments: dto.attachmentUrls?.length ?? 0,
    });
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendGeneralLinkValueCardBulk(dto, user.id);
      await this.responseService.success(res, 'Value card sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('general-link/value-card/single')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send the general-link ("pre-test") Value Card to a single recipient (email only)',
    description:
      "Top-up send for one general-link recipient the session's bulk run did not reach. The " +
      'recipient must have attended the session, the same gate the bulk audience applies. The ' +
      "description and attachments are read from the session's stored pretest_value_card_details — " +
      'NOT from this request — so every recipient of a session receives identical content, and the ' +
      'session is taken from the generated link itself. Requires that the general-link bulk send ' +
      'has already gone out for that session and that those stored details exist; rejects with ' +
      'VALUE_CARD_BULK_NOT_SENT or VALUE_CARD_DETAILS_MISSING otherwise.',
  })
  @ApiBody({ type: SendGeneralLinkValueCardSingleDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendGeneralLinkValueCardSingle(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendGeneralLinkValueCardSingleDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received single general-link value card communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendGeneralLinkValueCardSingle(dto, user.id);
      await this.responseService.success(res, 'Value card sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Post('single')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Send a single session communication (Absent / Invite / Value Card)',
    description: 'Sends the chosen communication to one registration of an online session.',
  })
  @ApiBody({ type: SendSingleSessionCommunicationDto })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({ status: HttpStatus.NOT_FOUND, description: SWAGGER_API_RESPONSE.NOT_FOUND })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async sendSingle(
    @Body(new ValidationPipe({ transform: true, whitelist: true }))
    dto: SendSingleSessionCommunicationDto,
    @Req() req: any,
    @Res() res: Response,
  ) {
    this.logger.log('Received single session communication request', dto);
    try {
      const user = extractAndValidateUser(req);
      const data = await this.service.sendSingle(dto, user.id);
      await this.responseService.success(res, 'Session communication sent successfully', data);
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  @Get('summary')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Get per-registration session communication counts',
    description:
      'Returns how many times each communication (per purpose and channel) was sent to each ' +
      'registration of a session.',
  })
  @ApiQuery({ name: 'programId', type: Number, required: true, description: 'Program id' })
  @ApiQuery({
    name: 'registrationIds',
    type: String,
    required: false,
    description: 'Optional comma-separated registration ids to filter',
  })
  @ApiResponse({ status: HttpStatus.OK, description: SWAGGER_API_RESPONSE.OK })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: SWAGGER_API_RESPONSE.BAD_REQUEST })
  @ApiResponse({
    status: HttpStatus.INTERNAL_SERVER_ERROR,
    description: SWAGGER_API_RESPONSE.INTERNAL_SERVER_ERROR,
  })
  async getSummary(
    @Query('programId') programId: number,
    @Query('registrationIds') registrationIds: string,
    @Res() res: Response,
  ) {
    this.logger.log('Received session communication summary request', {
      programId,
      registrationIds,
    });
    try {
      const ids = this.parseRegistrationIds(registrationIds);
      const data = await this.service.getSummary(Number(programId), ids);
      await this.responseService.success(
        res,
        'Session communication summary fetched successfully',
        {
          data,
        },
      );
    } catch (error) {
      handleControllerError(res, error);
    }
  }

  /**
   * Parse a comma-separated registrationIds query param into a number[] (or undefined).
   */
  private parseRegistrationIds(raw?: string): number[] | undefined {
    if (!raw) {
      return undefined;
    }
    const ids = raw
      .split(',')
      .map((value) => Number(value.trim()))
      .filter((value) => Number.isInteger(value) && value > 0);
    return ids.length > 0 ? ids : undefined;
  }
}
