import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Query,
  Req,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import type { Request } from 'express';
import { memoryStorage } from 'multer';
import {
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiOperation,
  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 '../../auth/jwt-auth.guard';
import { StakeholdersService } from './stakeholders.service';
import { AssignUserDto } from './dto/assign-user.dto';
import { CreateStakeholderDto } from './dto/create-stakeholder.dto';
import { CreateStakeholderTypeDto } from './dto/create-stakeholder-type.dto';
import { ListStakeholdersDto } from './dto/list-stakeholders.dto';
import { UpdateStakeholderDto } from './dto/update-stakeholder.dto';
import { UpdateStakeholderTypeDto } from './dto/update-stakeholder-type.dto';
import type { StakeholderDashboardSummary } from './stakeholder-dashboard-summary.types';

// Upload limit for a stakeholder logo: 5 MB.
const LOGO_MAX_BYTES = 5 * 1024 * 1024;

// Shared multer options for the logo upload interceptor.
const logoUploadOptions = {
  storage: memoryStorage(),
  limits: { fileSize: LOGO_MAX_BYTES },
};

// CASL subject these endpoints are authorized against.
const SUBJECT = 'Stakeholder';

@ApiTags('stakeholders')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('stakeholders')
export class StakeholdersController {
  constructor(private readonly stakeholders: StakeholdersService) {}

  @Get()
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'List all stakeholders' })
  @ApiResponse({ status: 200, description: 'List of stakeholders' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  list(@Query() query: ListStakeholdersDto) {
    return this.stakeholders.list(query);
  }

  @Get('types')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'List stakeholder types' })
  @ApiResponse({ status: 200, description: 'List of stakeholder types' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  listTypes() {
    return this.stakeholders.listTypes();
  }

  @Get('grouped-by-type')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'List stakeholder types with stakeholders' })
  @ApiResponse({
    status: 200,
    description: 'Stakeholder types with their stakeholders',
  })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  listGroupedByType() {
    return this.stakeholders.listGroupedByType();
  }

  @Get('working-groups')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'List working groups' })
  @ApiResponse({
    status: 200,
    description: 'Active Private Sector stakeholders for working group filters',
  })
  listWorkingGroups() {
    return this.stakeholders.listWorkingGroups();
  }

  @Get('summary')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get stakeholder dashboard summary' })
  @ApiResponse({
    status: 200,
    description: 'Global stakeholder dashboard counts',
  })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  getDashboardSummary(): Promise<StakeholderDashboardSummary> {
    return this.stakeholders.getDashboardSummary();
  }

  // --- Stakeholder type CRUD -------------------------------------------
  // The `/types/:id` routes are declared here (before the `:id` routes
  // lower down) so Express matches the more specific path first.

  @Post('types')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Create, SUBJECT))
  @ApiOperation({ summary: 'Create a stakeholder type' })
  @ApiResponse({ status: 201, description: 'Stakeholder type created' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 409, description: 'Type name already exists' })
  createType(
    // The JWT guard attaches `user` to the request. We type it inline so
    // there is no separate interface file to maintain.
    @Req() req: Request & { user: { id: number } },
    @Body() dto: CreateStakeholderTypeDto,
  ) {
    return this.stakeholders.createType(req.user.id, dto);
  }

  @Get('types/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get one stakeholder type' })
  @ApiResponse({ status: 200, description: 'Stakeholder type detail' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder type not found' })
  getType(@Param('id', ParseIntPipe) id: number) {
    return this.stakeholders.getType(id);
  }

  @Patch('types/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Update a stakeholder type' })
  @ApiResponse({ status: 200, description: 'Stakeholder type updated' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder type not found' })
  @ApiResponse({ status: 409, description: 'Type name already exists' })
  updateType(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateStakeholderTypeDto,
  ) {
    return this.stakeholders.updateType(id, dto);
  }

  @Delete('types/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Delete, SUBJECT))
  @ApiOperation({ summary: 'Soft delete a stakeholder type' })
  @ApiResponse({ status: 200, description: 'Stakeholder type deleted' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder type not found' })
  removeType(@Param('id', ParseIntPipe) id: number) {
    return this.stakeholders.removeType(id);
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get a single stakeholder with its members' })
  @ApiResponse({ status: 200, description: 'The stakeholder' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder not found' })
  getOne(@Param('id', ParseIntPipe) id: number) {
    return this.stakeholders.getOne(id);
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Create, SUBJECT))
  @UseInterceptors(FileInterceptor('logoFile', logoUploadOptions))
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: CreateStakeholderDto })
  @ApiOperation({ summary: 'Create a new stakeholder' })
  @ApiResponse({ status: 201, description: 'Stakeholder created' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  create(
    @Body() dto: CreateStakeholderDto,
    @UploadedFile() logoFile?: Express.Multer.File,
  ) {
    return this.stakeholders.create(dto, logoFile);
  }

  @Patch(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @UseInterceptors(FileInterceptor('logoFile', logoUploadOptions))
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: UpdateStakeholderDto })
  @ApiOperation({ summary: 'Update a stakeholder' })
  @ApiResponse({ status: 200, description: 'Stakeholder updated' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder not found' })
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateStakeholderDto,
    @UploadedFile() logoFile?: Express.Multer.File,
  ) {
    return this.stakeholders.update(id, dto, logoFile);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Delete, SUBJECT))
  @ApiOperation({ summary: 'Soft delete a stakeholder' })
  @ApiResponse({ status: 200, description: 'Stakeholder deleted' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder not found' })
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.stakeholders.remove(id);
  }

  @Get(':id/users')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'List users that belong to a stakeholder' })
  @ApiResponse({ status: 200, description: 'List of members' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder not found' })
  listMembers(@Param('id', ParseIntPipe) id: number) {
    return this.stakeholders.listMembers(id);
  }

  @Post(':id/users')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Add a user to a stakeholder' })
  @ApiResponse({ status: 201, description: 'User added' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'Stakeholder or user not found' })
  @ApiResponse({ status: 409, description: 'User is already a member' })
  addUser(@Param('id', ParseIntPipe) id: number, @Body() dto: AssignUserDto) {
    return this.stakeholders.addUser(id, dto);
  }

  @Delete(':id/users/:userId')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Remove a user from a stakeholder' })
  @ApiResponse({ status: 200, description: 'User removed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({
    status: 404,
    description: 'Stakeholder or membership not found',
  })
  removeUser(
    @Param('id', ParseIntPipe) id: number,
    @Param('userId', ParseIntPipe) userId: number,
  ) {
    return this.stakeholders.removeUser(id, userId);
  }
}
