import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Query,
  Req,
  UnauthorizedException,
  UseGuards,
} from '@nestjs/common';
import type { Request } from 'express';

import {
  ApiBearerAuth,
  ApiBody,
  ApiOperation,
  ApiParam,
  ApiQuery,
  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 { CreateMeetingDto } from './dto/create-meeting.dto';
import { ListMeetingsDto } from './dto/list-meetings.dto';
import { UpdateMeetingDto } from './dto/update-meeting.dto';
import { MeetingsService } from './meetings.service';

// CASL subject used by every route in this controller. Access comes from the
// role's table permissions (e.g. "read Meeting"), not a hardcoded role list.
const MEETING_SUBJECT = 'Meeting';

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

@ApiTags('meetings')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('meetings')
export class MeetingsController {
  constructor(private readonly meetingsService: MeetingsService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, MEETING_SUBJECT),
  )
  @ApiOperation({ summary: 'Create a ministry meeting' })
  @ApiBody({ type: CreateMeetingDto })
  @ApiResponse({
    status: 201,
    description: 'Meeting created successfully.',
  })
  @ApiResponse({
    status: 400,
    description:
      'Validation failed, user not found, or meeting request not found.',
  })
  @ApiResponse({
    status: 401,
    description: 'Missing or invalid token.',
  })
  @ApiResponse({
    status: 403,
    description: 'Insufficient permissions.',
  })
  create(@Req() request: AuthenticatedRequest, @Body() dto: CreateMeetingDto) {
    return this.meetingsService.create(dto, this.getCurrentUserId(request));
  }

  @Get()
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, MEETING_SUBJECT),
  )
  @ApiOperation({ summary: 'List ministry meetings' })
  @ApiQuery({
    name: 'page',
    required: false,
    type: Number,
    description: 'Page number. Default is 1.',
  })
  @ApiQuery({
    name: 'limit',
    required: false,
    type: Number,
    description: 'Items per page. Default is 10.',
  })
  @ApiQuery({
    name: 'userId',
    required: false,
    type: Number,
    description: 'Filter meetings by owner user ID.',
  })
  @ApiQuery({
    name: 'meetingRequestId',
    required: false,
    type: Number,
    description: 'Filter meetings by linked Meeting Request ID.',
  })
  @ApiResponse({
    status: 200,
    description: 'List of meetings.',
  })
  findAll(@Query() query: ListMeetingsDto) {
    return this.meetingsService.findAll(query);
  }

  // Guest users are looked up while organizing a meeting, so this needs the
  // same permission as creating one.
  @Get('meeting-requests/:meetingRequestId/guest-users')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, MEETING_SUBJECT),
  )
  @ApiOperation({
    summary: 'List users from government agencies on a Meeting Request',
  })
  @ApiParam({
    name: 'meetingRequestId',
    type: Number,
    description: 'Meeting Request ID.',
  })
  @ApiResponse({
    status: 200,
    description: 'Users linked to government agencies.',
  })
  findMeetingRequestGuestUsers(
    @Req() request: AuthenticatedRequest,
    @Param('meetingRequestId', ParseIntPipe) meetingRequestId: number,
  ) {
    return this.meetingsService.findMeetingRequestGuestUsers(
      meetingRequestId,
      this.getCurrentUserId(request),
    );
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, MEETING_SUBJECT),
  )
  @ApiOperation({ summary: 'Get one ministry meeting' })
  @ApiParam({
    name: 'id',
    type: Number,
    description: 'Meeting ID.',
  })
  @ApiResponse({
    status: 200,
    description: 'Meeting detail.',
  })
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.meetingsService.findOne(id);
  }

  @Patch(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, MEETING_SUBJECT),
  )
  @ApiOperation({ summary: 'Update a ministry meeting' })
  @ApiParam({
    name: 'id',
    type: Number,
    description: 'Meeting ID.',
  })
  @ApiBody({ type: UpdateMeetingDto })
  @ApiResponse({
    status: 200,
    description: 'Meeting updated successfully.',
  })
  update(
    @Req() request: AuthenticatedRequest,
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateMeetingDto,
  ) {
    return this.meetingsService.update(id, dto, this.getCurrentUserId(request));
  }

  @Delete(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Delete, MEETING_SUBJECT),
  )
  @ApiOperation({ summary: 'Delete a ministry meeting' })
  @ApiParam({
    name: 'id',
    type: Number,
    description: 'Meeting ID.',
  })
  @ApiResponse({
    status: 200,
    description: 'Meeting deleted successfully.',
  })
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.meetingsService.remove(id);
  }

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

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

    return userId;
  }
}
