import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Req,
  UnauthorizedException,
  UseGuards,
} from '@nestjs/common';
import type { Request } from 'express';
import {
  ApiBearerAuth,
  ApiBody,
  ApiOperation,
  ApiParam,
  ApiResponse,
  ApiTags,
} from '@nestjs/swagger';

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 { UpdateProgressReportDeadlineDto } from './dto/update-progress-report-deadline.dto';
import { ProgressReportsService } from './progress-reports.service';

// One deadline is its own resource, so it does not need the report ID in the
// URL. Permissions still use the "ProgressReport" subject.
const PROGRESS_REPORT_SUBJECT = 'ProgressReport';

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

@ApiTags('progress-report-deadlines')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('progress-report-deadlines')
export class ProgressReportDeadlinesController {
  constructor(
    private readonly progressReportsService: ProgressReportsService,
  ) {}

  @Get(':deadlineId')
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Get one progress report deadline' })
  @ApiParam({ name: 'deadlineId', type: Number })
  @ApiResponse({ status: 200, description: 'Deadline detail.' })
  @ApiResponse({ status: 404, description: 'Deadline not found.' })
  findOne(@Param('deadlineId', ParseIntPipe) deadlineId: number) {
    return this.progressReportsService.findDeadline(deadlineId);
  }

  @Patch(':deadlineId')
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, PROGRESS_REPORT_SUBJECT),
  )
  @ApiBody({ type: UpdateProgressReportDeadlineDto })
  @ApiOperation({ summary: 'Change one progress report deadline date' })
  @ApiParam({ name: 'deadlineId', type: Number })
  @ApiResponse({ status: 200, description: 'Deadline updated.' })
  @ApiResponse({ status: 400, description: 'The date breaks the slot order.' })
  @ApiResponse({ status: 404, description: 'Deadline not found.' })
  update(
    @Param('deadlineId', ParseIntPipe) deadlineId: number,
    @Body() dto: UpdateProgressReportDeadlineDto,
  ) {
    return this.progressReportsService.updateDeadline(deadlineId, dto);
  }

  @Patch(':deadlineId/send')
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, PROGRESS_REPORT_SUBJECT),
  )
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Send one progress report deadline' })
  @ApiParam({ name: 'deadlineId', type: Number })
  @ApiResponse({ status: 200, description: 'Deadline sent.' })
  @ApiResponse({ status: 404, description: 'Deadline not found.' })
  @ApiResponse({ status: 409, description: 'Workflow order is invalid.' })
  send(
    @Param('deadlineId', ParseIntPipe) deadlineId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.progressReportsService.sendDeadline(
      deadlineId,
      this.getUserId(request),
    );
  }

  @Delete(':deadlineId')
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Delete, PROGRESS_REPORT_SUBJECT),
  )
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Soft delete one progress report deadline' })
  @ApiParam({ name: 'deadlineId', type: Number })
  @ApiResponse({ status: 200, description: 'Deadline deleted.' })
  @ApiResponse({ status: 404, description: 'Deadline not found.' })
  @ApiResponse({ status: 409, description: 'The first deadline is required.' })
  remove(@Param('deadlineId', ParseIntPipe) deadlineId: number) {
    return this.progressReportsService.removeDeadline(deadlineId);
  }

  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;
  }
}
