import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Put,
  Query,
  Req,
  UnauthorizedException,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiOperation,
  ApiParam,
  ApiTags,
} from '@nestjs/swagger';
import type { Request } from 'express';

import { Action } from '@/casl/action.enum';
import { AppAbility } from '@/casl/casl-ability.factory';
import { CheckPolicies } from '@/casl/check-policies.decorator';
import { PoliciesGuard } from '@/casl/policies.guard';
import { JwtAuthGuard } from '@/modules/auth/jwt-auth.guard';
import { pdfUploadOptions } from '@/modules/upload/pdf-upload.options';

import { QueryProgressReportMinistriesDto } from './dto/query-progress-report-ministries.dto';
import { ReviewProgressReportMinistryDto } from './dto/review-progress-report-ministry.dto';
import { SaveProgressReportIssueCommentDto } from './dto/save-progress-report-issue-comment.dto';
import { UpdateProgressReportIssueStatusDto } from './dto/update-progress-report-issue-status.dto';
import { UpdateMyProgressReportMinistryDto } from './dto/update-my-progress-report-ministry.dto';
import { UpsertMyProgressReportIssueDto } from './dto/upsert-my-progress-report-issue.dto';
import { UpsertMyProgressReportRgcDecisionDto } from './dto/upsert-my-progress-report-rgc-decision.dto';
import { UpdateProgressReportRgcDecisionStatusDto } from './dto/update-progress-report-rgc-decision-status.dto';
import { MinistryProgressReportsService } from './ministry-progress-reports.service';

type AuthenticatedRequest = Request & {
  user?: { id?: number };
};

const SUBJECT = 'ProgressReportMinistry';
const MAX_MINISTRY_REPORT_SIZE = 10 * 1024 * 1024;

@ApiTags('progress-report-ministries')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('progress-reports/:progressReportId/ministries')
export class MinistryProgressReportsController {
  constructor(private readonly service: MinistryProgressReportsService) {}

  @Get()
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'List Ministry assignments for a progress report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  findAll(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Query() query: QueryProgressReportMinistriesDto,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.findAll(
      progressReportId,
      query,
      this.getUserId(request),
    );
  }

  @Get('me')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get the current Ministry assignment' })
  @ApiParam({ name: 'progressReportId', type: Number })
  findMine(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.findMine(progressReportId, this.getUserId(request));
  }

  @Patch('me')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @UseInterceptors(
    FileInterceptor('attachment', pdfUploadOptions(MAX_MINISTRY_REPORT_SIZE)),
  )
  @ApiConsumes('multipart/form-data')
  @ApiBody({ type: UpdateMyProgressReportMinistryDto })
  @ApiOperation({ summary: 'Update the current Ministry assignment' })
  @ApiParam({ name: 'progressReportId', type: Number })
  updateMine(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: UpdateMyProgressReportMinistryDto,
    @UploadedFile() attachment?: Express.Multer.File,
  ) {
    return this.service.updateMine(
      progressReportId,
      this.getUserId(request),
      dto,
      attachment,
    );
  }

  @Post('me/submit')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Submit the current Ministry report to CDC-GPSF' })
  @ApiParam({ name: 'progressReportId', type: Number })
  submitMine(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.submitMine(progressReportId, this.getUserId(request));
  }

  @Put('me/issues/:issueId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @UseInterceptors(
    FileInterceptor('attachment', pdfUploadOptions(MAX_MINISTRY_REPORT_SIZE)),
  )
  @ApiConsumes('multipart/form-data')
  @ApiBody({ type: UpsertMyProgressReportIssueDto })
  @ApiOperation({
    summary: 'Create or update one Ministry Issue progress update',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'issueId', type: Number })
  upsertMyIssue(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('issueId', ParseIntPipe) issueId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: UpsertMyProgressReportIssueDto,
    @UploadedFile() attachment?: Express.Multer.File,
  ) {
    return this.service.upsertMyIssue(
      progressReportId,
      issueId,
      this.getUserId(request),
      dto,
      attachment,
    );
  }

  @Patch('me/rgc-decisions/:plenaryDecisionId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @UseInterceptors(
    FileInterceptor('attachment', pdfUploadOptions(MAX_MINISTRY_REPORT_SIZE)),
  )
  @ApiConsumes('multipart/form-data')
  @ApiBody({ type: UpsertMyProgressReportRgcDecisionDto })
  @ApiOperation({
    summary: 'Create or update one Ministry RGC Decision progress update',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'plenaryDecisionId', type: Number })
  upsertMyRgcDecision(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('plenaryDecisionId', ParseIntPipe) plenaryDecisionId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: UpsertMyProgressReportRgcDecisionDto,
    @UploadedFile() attachment?: Express.Multer.File,
  ) {
    return this.service.upsertMyRgcDecision(
      progressReportId,
      plenaryDecisionId,
      this.getUserId(request),
      dto,
      attachment,
    );
  }

  @Post(':ministryId/issues/:issueId/comments')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Add a CDC comment to one Progress Report Issue' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'issueId', type: Number })
  createIssueComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('issueId', ParseIntPipe) issueId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: SaveProgressReportIssueCommentDto,
  ) {
    return this.service.createIssueComment(
      progressReportId,
      ministryId,
      issueId,
      this.getUserId(request),
      dto,
    );
  }

  @Patch(':ministryId/issues/:issueId/comments/:commentId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Edit a CDC Progress Report Issue comment' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'issueId', type: Number })
  @ApiParam({ name: 'commentId', type: Number })
  updateIssueComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('issueId', ParseIntPipe) issueId: number,
    @Param('commentId', ParseIntPipe) commentId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: SaveProgressReportIssueCommentDto,
  ) {
    return this.service.updateIssueComment(
      progressReportId,
      ministryId,
      issueId,
      commentId,
      this.getUserId(request),
      dto,
    );
  }

  @Delete(':ministryId/issues/:issueId/comments/:commentId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Delete a CDC Progress Report Issue comment' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'issueId', type: Number })
  @ApiParam({ name: 'commentId', type: Number })
  deleteIssueComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('issueId', ParseIntPipe) issueId: number,
    @Param('commentId', ParseIntPipe) commentId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.deleteIssueComment(
      progressReportId,
      ministryId,
      issueId,
      commentId,
      this.getUserId(request),
    );
  }

  @Post(':ministryId/rgc-decisions/:plenaryDecisionId/comments')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({
    summary: 'Add a CDC comment to one Progress Report RGC Decision',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'plenaryDecisionId', type: Number })
  createRgcDecisionComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('plenaryDecisionId', ParseIntPipe) plenaryDecisionId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: SaveProgressReportIssueCommentDto,
  ) {
    return this.service.createRgcDecisionComment(
      progressReportId,
      ministryId,
      plenaryDecisionId,
      this.getUserId(request),
      dto,
    );
  }

  @Patch(':ministryId/rgc-decisions/:plenaryDecisionId/comments/:commentId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Edit a CDC Progress Report RGC Decision comment' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'plenaryDecisionId', type: Number })
  @ApiParam({ name: 'commentId', type: Number })
  updateRgcDecisionComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('plenaryDecisionId', ParseIntPipe) plenaryDecisionId: number,
    @Param('commentId', ParseIntPipe) commentId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: SaveProgressReportIssueCommentDto,
  ) {
    return this.service.updateRgcDecisionComment(
      progressReportId,
      ministryId,
      plenaryDecisionId,
      commentId,
      this.getUserId(request),
      dto,
    );
  }

  @Delete(':ministryId/rgc-decisions/:plenaryDecisionId/comments/:commentId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({
    summary: 'Delete a CDC Progress Report RGC Decision comment',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'plenaryDecisionId', type: Number })
  @ApiParam({ name: 'commentId', type: Number })
  deleteRgcDecisionComment(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('plenaryDecisionId', ParseIntPipe) plenaryDecisionId: number,
    @Param('commentId', ParseIntPipe) commentId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.deleteRgcDecisionComment(
      progressReportId,
      ministryId,
      plenaryDecisionId,
      commentId,
      this.getUserId(request),
    );
  }

  @Patch(':ministryId/issues/:issueId/status')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({
    summary: 'Update one Progress Report Issue status as CDC-GPSF',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'issueId', type: Number })
  updateIssueStatus(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('issueId', ParseIntPipe) issueId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: UpdateProgressReportIssueStatusDto,
  ) {
    return this.service.updateIssueStatus(
      progressReportId,
      ministryId,
      issueId,
      this.getUserId(request),
      dto,
    );
  }

  @Patch(':ministryId/rgc-decisions/:plenaryDecisionId/status')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({
    summary: 'Update one Progress Report RGC Decision status as CDC-GPSF',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  @ApiParam({ name: 'plenaryDecisionId', type: Number })
  updateRgcDecisionStatus(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Param('plenaryDecisionId', ParseIntPipe) plenaryDecisionId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: UpdateProgressReportRgcDecisionStatusDto,
  ) {
    return this.service.updateRgcDecisionStatus(
      progressReportId,
      ministryId,
      plenaryDecisionId,
      this.getUserId(request),
      dto,
    );
  }

  @Get(':ministryId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get one Ministry assignment for CDC-GPSF' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  findOne(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.service.findOne(
      progressReportId,
      ministryId,
      this.getUserId(request),
    );
  }

  @Patch(':ministryId')
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Review one Ministry assignment as CDC-GPSF' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiParam({ name: 'ministryId', type: Number })
  review(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
    @Param('ministryId', ParseIntPipe) ministryId: number,
    @Req() request: AuthenticatedRequest,
    @Body() dto: ReviewProgressReportMinistryDto,
  ) {
    return this.service.review(
      progressReportId,
      ministryId,
      this.getUserId(request),
      dto,
    );
  }

  private getUserId(request: AuthenticatedRequest): number {
    const userId = Number(request.user?.id);

    if (!Number.isInteger(userId) || userId <= 0) {
      throw new UnauthorizedException('Invalid authenticated user.');
    }

    return userId;
  }
}
