import {
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Post,
  Req,
  UnauthorizedException,
  UseGuards,
} from '@nestjs/common';
import {
  ApiBearerAuth,
  ApiOperation,
  ApiParam,
  ApiResponse,
  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 {
  ProgressReportDashboardStatus,
  ProgressReportDashboardType,
} from '@/generated/prisma/client';
import { JwtAuthGuard } from '@/modules/auth/jwt-auth.guard';

import { ProgressReportDashboardsService } from './dashboards.service';

// The dashboard belongs to the report, so it reuses the "ProgressReport"
// permission subject: no new permission has to be seeded.
const PROGRESS_REPORT_SUBJECT = 'ProgressReport';

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

@ApiTags('progress-report-dashboards')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('progress-reports/:progressReportId/dashboards')
export class ProgressReportDashboardsController {
  constructor(
    private readonly dashboardsService: ProgressReportDashboardsService,
  ) {}

  // The PSWG (working group) draft dashboard. The Plenary dashboard gets its
  // own route later, because its numbers come from the RGC decisions.
  @Get('pswg')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Get the PSWG draft dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 200, description: 'Draft dashboard numbers.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  findPswg(@Param('progressReportId', ParseIntPipe) progressReportId: number) {
    return this.dashboardsService.findOne(
      progressReportId,
      ProgressReportDashboardType.PSWG,
    );
  }

  // "Generate Draft Dashboard → PSWG": recalculates from the reports the
  // ministries submitted and saves the result as a draft.
  @Post('pswg')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Generate the PSWG draft dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Draft dashboard generated.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  generatePswg(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.generate(
      progressReportId,
      ProgressReportDashboardType.PSWG,
      this.getUserId(request),
    );
  }

  // The PSWG final dashboard. Same numbers as the draft, saved on its own so
  // generating the final never overwrites the draft.
  @Get('pswg/final')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Get the PSWG final dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 200, description: 'Final dashboard numbers.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  findPswgFinal(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.findOne(
      progressReportId,
      ProgressReportDashboardType.PSWG,
      ProgressReportDashboardStatus.FINAL,
    );
  }

  // "Generate Final Dashboard → PSWG".
  @Post('pswg/final')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Generate the PSWG final dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Final dashboard generated.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  generatePswgFinal(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.generate(
      progressReportId,
      ProgressReportDashboardType.PSWG,
      this.getUserId(request),
      ProgressReportDashboardStatus.FINAL,
    );
  }

  // The Plenary dashboard. Same four routes as PSWG, but the numbers come from
  // the RGC decisions instead of the ministries' issues.
  @Get('plenary')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Get the Plenary draft dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 200, description: 'Draft dashboard numbers.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  findPlenary(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.findOne(
      progressReportId,
      ProgressReportDashboardType.PLENARY,
    );
  }

  @Post('plenary')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Generate the Plenary draft dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Draft dashboard generated.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  generatePlenary(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.generate(
      progressReportId,
      ProgressReportDashboardType.PLENARY,
      this.getUserId(request),
    );
  }

  @Get('plenary/final')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Get the Plenary final dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 200, description: 'Final dashboard numbers.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  findPlenaryFinal(
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.findOne(
      progressReportId,
      ProgressReportDashboardType.PLENARY,
      ProgressReportDashboardStatus.FINAL,
    );
  }

  @Post('plenary/final')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Generate the Plenary final dashboard of a report' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Final dashboard generated.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  generatePlenaryFinal(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.generate(
      progressReportId,
      ProgressReportDashboardType.PLENARY,
      this.getUserId(request),
      ProgressReportDashboardStatus.FINAL,
    );
  }

  // "Live": makes the generated final dashboard the one every role dashboard
  // shows. Nothing is recounted here — the saved final numbers are copied.
  @Post('pswg/live')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({ summary: 'Publish the PSWG final dashboard as the live one' })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Dashboard published live.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  @ApiResponse({ status: 409, description: 'The final dashboard is missing.' })
  publishPswgLive(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.publishLive(
      progressReportId,
      ProgressReportDashboardType.PSWG,
      this.getUserId(request),
    );
  }

  // "Live" for the plenary dashboard: the generated final numbers become the
  // ones every plenary dashboard shows.
  @Post('plenary/live')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, PROGRESS_REPORT_SUBJECT),
  )
  @ApiOperation({
    summary: 'Publish the Plenary final dashboard as the live one',
  })
  @ApiParam({ name: 'progressReportId', type: Number })
  @ApiResponse({ status: 201, description: 'Dashboard published live.' })
  @ApiResponse({ status: 404, description: 'Progress report not found.' })
  @ApiResponse({ status: 409, description: 'The final dashboard is missing.' })
  publishPlenaryLive(
    @Req() request: AuthenticatedRequest,
    @Param('progressReportId', ParseIntPipe) progressReportId: number,
  ) {
    return this.dashboardsService.publishLive(
      progressReportId,
      ProgressReportDashboardType.PLENARY,
      this.getUserId(request),
    );
  }

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