import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Query,
  Req,
  Res,
  StreamableFile,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import type { Request, Response } 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 '@/modules/auth/jwt-auth.guard';
import { WorkingGroupIssuesService } from './working-group-issues.service';
import type {
  GovernmentAgencyOption,
  CategoryDetail,
  CategoryMutationResponse,
  CategoryOption,
  IssueStatusDetail,
  IssueStatusMutationResponse,
  IssueStatusOption,
  MessageResponse,
  PaginatedWorkingGroupIssuesResponse,
  WorkingGroupIssuePrimaryGovernment,
  WorkingGroupIssueMutationResponse,
  WorkingGroupIssueResponse,
  WorkingGroupIssueSummary,
} from './working-group-issues.service';
import { CreateCategoryDto } from './dto/create-category.dto';
import { CreateIssueStatusDto } from './dto/create-issue-status.dto';
import { CreateWorkingGroupIssueDto } from './dto/create-working-group-issue.dto';
import { ExportWorkingGroupIssuesDto } from './dto/export-working-group-issues.dto';
import { QueryWorkingGroupIssuesDto } from './dto/query-working-group-issue.dto';
import { UpdateCategoryDto } from './dto/update-category.dto';
import { UpdateIssueStatusDto } from './dto/update-issue-status.dto';
import { UpdateWorkingGroupIssueDto } from './dto/update-working-group-issue.dto';

// CASL subjects used by this module. Working group issue screens and issue
// matrix screens now have separate permissions.
const WORKING_GROUP_ISSUE_SUBJECT = 'WorkingGroupIssue';
const ISSUE_MATRIX_SUBJECT = 'IssueMatrix';

// Upload limit for an issue attachment: 10 MB.
const ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;

// Shared multer options for the file upload interceptor.
const attachmentUploadOptions = {
  storage: memoryStorage(),
  limits: { fileSize: ATTACHMENT_MAX_BYTES },
};

@ApiTags('working-group-issues')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('working-group-issues')
export class WorkingGroupIssuesController {
  constructor(
    private readonly workingGroupIssuesService: WorkingGroupIssuesService,
  ) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @UseInterceptors(FileInterceptor('attachmentFile', attachmentUploadOptions))
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: CreateWorkingGroupIssueDto })
  @ApiOperation({ summary: 'Create a working group issue' })
  @ApiResponse({ status: 201, description: 'Working group issue created' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  create(
    // 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() createWorkingGroupIssueDto: CreateWorkingGroupIssueDto,
    @UploadedFile() attachmentFile?: Express.Multer.File,
  ): Promise<WorkingGroupIssueMutationResponse> {
    return this.workingGroupIssuesService.create(
      req.user.id,
      createWorkingGroupIssueDto,
      attachmentFile,
    );
  }

  @Get()
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary:
      'List working group issues with pagination, search, sorting, and filters',
  })
  @ApiResponse({
    status: 200,
    description: 'Paginated list of working group issues',
  })
  findAll(
    @Query() query: QueryWorkingGroupIssuesDto,
  ): Promise<PaginatedWorkingGroupIssuesResponse> {
    return this.workingGroupIssuesService.findAll(query);
  }

  @Get('my/primary-goverment')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary: "Get current user's working group and primary government agency",
  })
  @ApiResponse({
    status: 200,
    description: 'Current working group and primary government agency',
  })
  getMyPrimaryGovernment(
    @Req() req: Request & { user: { id: number } },
  ): Promise<WorkingGroupIssuePrimaryGovernment> {
    return this.workingGroupIssuesService.getMyPrimaryGovernment(req.user.id);
  }

  @Get('my')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary: "List issues owned by the current user's working group",
  })
  @ApiResponse({
    status: 200,
    description: 'Paginated list of current working group issues',
  })
  findMyIssues(
    @Req() req: Request & { user: { id: number } },
    @Query() query: QueryWorkingGroupIssuesDto,
  ): Promise<PaginatedWorkingGroupIssuesResponse> {
    return this.workingGroupIssuesService.findMyIssues(req.user.id, query);
  }

  @Get('issue-matrix')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, ISSUE_MATRIX_SUBJECT),
  )
  @ApiOperation({
    summary: 'List issues from other private-sector working groups',
  })
  @ApiResponse({
    status: 200,
    description: 'Paginated list of issue matrix items',
  })
  findIssueMatrix(
    @Query() query: QueryWorkingGroupIssuesDto,
  ): Promise<PaginatedWorkingGroupIssuesResponse> {
    return this.workingGroupIssuesService.findIssueMatrix(query);
  }

  @Get('issue-matrix/summary')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, ISSUE_MATRIX_SUBJECT),
  )
  @ApiOperation({
    summary:
      'Aggregate summary for issues from other private-sector working groups',
  })
  @ApiResponse({
    status: 200,
    description: 'Issue matrix summary counts',
  })
  getIssueMatrixSummary(): Promise<WorkingGroupIssueSummary> {
    return this.workingGroupIssuesService.getIssueMatrixSummary();
  }

  @Get('issue-matrix/export')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Export, ISSUE_MATRIX_SUBJECT),
  )
  @ApiOperation({
    summary: 'Export issue matrix issues as Excel',
  })
  @ApiResponse({
    status: 200,
    description: 'Issue matrix Excel file download',
  })
  async exportIssueMatrix(
    @Query() query: ExportWorkingGroupIssuesDto,
    @Res({ passthrough: true }) res: Response,
  ): Promise<StreamableFile> {
    const { buffer, filename } =
      await this.workingGroupIssuesService.exportIssueMatrix(query);

    res.set({
      'Content-Type':
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'Content-Disposition': `attachment; filename="${filename}"`,
    });

    return new StreamableFile(buffer);
  }

  @Get('my/export')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Export, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary: "Export current user's working group issues as Excel",
  })
  @ApiResponse({
    status: 200,
    description: 'Excel file download',
  })
  async exportMyIssues(
    @Req() req: Request & { user: { id: number } },
    @Query() query: ExportWorkingGroupIssuesDto,
    @Res({ passthrough: true }) res: Response,
  ): Promise<StreamableFile> {
    const { buffer, filename } =
      await this.workingGroupIssuesService.exportMyIssues(req.user.id, query);

    res.set({
      'Content-Type':
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'Content-Disposition': `attachment; filename="${filename}"`,
    });

    return new StreamableFile(buffer);
  }

  @Get('statuses')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'List issue statuses for forms' })
  @ApiResponse({ status: 200, description: 'Issue statuses' })
  listIssueStatuses(): Promise<IssueStatusOption[]> {
    return this.workingGroupIssuesService.listIssueStatuses();
  }

  @Get('summary')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary:
      'Aggregate counts of working group issues (total + by status bucket)',
  })
  @ApiResponse({ status: 200, description: 'Working group issues summary' })
  getSummary(): Promise<WorkingGroupIssueSummary> {
    return this.workingGroupIssuesService.getSummary();
  }

  @Get('summary/my')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({
    summary: "Aggregate counts for the current user's working group",
  })
  @ApiResponse({
    status: 200,
    description: 'Current working group issues summary',
  })
  getMySummary(
    @Req() req: Request & { user: { id: number } },
  ): Promise<WorkingGroupIssueSummary> {
    return this.workingGroupIssuesService.getMySummary(req.user.id);
  }

  @Get('categories')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'List issue categories for forms' })
  @ApiResponse({ status: 200, description: 'Issue categories' })
  listCategories(): Promise<CategoryOption[]> {
    return this.workingGroupIssuesService.listCategories();
  }

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

  @Post('statuses')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Create an issue status' })
  @ApiResponse({ status: 201, description: 'Issue status created' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 409, description: 'Status name already exists' })
  createStatus(
    // 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: CreateIssueStatusDto,
  ): Promise<IssueStatusMutationResponse> {
    return this.workingGroupIssuesService.createStatus(req.user.id, dto);
  }

  @Get('statuses/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Get one issue status' })
  @ApiResponse({ status: 200, description: 'Issue status detail' })
  @ApiResponse({ status: 404, description: 'Issue status not found' })
  getStatus(@Param('id', ParseIntPipe) id: number): Promise<IssueStatusDetail> {
    return this.workingGroupIssuesService.getStatus(id);
  }

  @Patch('statuses/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Update an issue status' })
  @ApiResponse({ status: 200, description: 'Issue status updated' })
  @ApiResponse({ status: 404, description: 'Issue status not found' })
  @ApiResponse({ status: 409, description: 'Status name already exists' })
  updateStatus(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateIssueStatusDto,
  ): Promise<IssueStatusMutationResponse> {
    return this.workingGroupIssuesService.updateStatus(id, dto);
  }

  @Delete('statuses/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Delete, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Soft delete an issue status' })
  @ApiResponse({ status: 200, description: 'Issue status deleted' })
  @ApiResponse({ status: 404, description: 'Issue status not found' })
  removeStatus(
    @Param('id', ParseIntPipe) id: number,
  ): Promise<MessageResponse> {
    return this.workingGroupIssuesService.removeStatus(id);
  }

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

  @Post('categories')
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Create, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Create an issue category' })
  @ApiResponse({ status: 201, description: 'Issue category created' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 409, description: 'Category name already exists' })
  createCategory(
    // 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: CreateCategoryDto,
  ): Promise<CategoryMutationResponse> {
    return this.workingGroupIssuesService.createCategory(req.user.id, dto);
  }

  @Get('categories/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Get one issue category' })
  @ApiResponse({ status: 200, description: 'Issue category detail' })
  @ApiResponse({ status: 404, description: 'Issue category not found' })
  getCategory(@Param('id', ParseIntPipe) id: number): Promise<CategoryDetail> {
    return this.workingGroupIssuesService.getCategory(id);
  }

  @Patch('categories/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Update an issue category' })
  @ApiResponse({ status: 200, description: 'Issue category updated' })
  @ApiResponse({ status: 404, description: 'Issue category not found' })
  @ApiResponse({ status: 409, description: 'Category name already exists' })
  updateCategory(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateCategoryDto,
  ): Promise<CategoryMutationResponse> {
    return this.workingGroupIssuesService.updateCategory(id, dto);
  }

  @Delete('categories/:id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Delete, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Soft delete an issue category' })
  @ApiResponse({ status: 200, description: 'Issue category deleted' })
  @ApiResponse({ status: 404, description: 'Issue category not found' })
  removeCategory(
    @Param('id', ParseIntPipe) id: number,
  ): Promise<MessageResponse> {
    return this.workingGroupIssuesService.removeCategory(id);
  }

  @Get('government-agencies')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'List government agencies for issue forms' })
  @ApiResponse({ status: 200, description: 'Government agencies' })
  listGovernmentAgencies(): Promise<GovernmentAgencyOption[]> {
    return this.workingGroupIssuesService.listGovernmentAgencies();
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Read, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Get one working group issue' })
  @ApiResponse({ status: 200, description: 'Working group issue detail' })
  @ApiResponse({ status: 404, description: 'Working group issue not found' })
  findOne(
    @Req() req: Request & { user: { id: number } },
    @Param('id', ParseIntPipe) id: number,
  ): Promise<WorkingGroupIssueResponse> {
    return this.workingGroupIssuesService.findOne(id, req.user.id);
  }

  @Patch(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Update, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @UseInterceptors(FileInterceptor('attachmentFile', attachmentUploadOptions))
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: UpdateWorkingGroupIssueDto })
  @ApiOperation({ summary: 'Update a working group issue' })
  @ApiResponse({ status: 200, description: 'Working group issue updated' })
  @ApiResponse({ status: 404, description: 'Working group issue not found' })
  update(
    @Req() req: Request & { user: { id: number } },
    @Param('id', ParseIntPipe) id: number,
    @Body() updateWorkingGroupIssueDto: UpdateWorkingGroupIssueDto,
    @UploadedFile() attachmentFile?: Express.Multer.File,
  ): Promise<WorkingGroupIssueMutationResponse> {
    return this.workingGroupIssuesService.update(
      id,
      updateWorkingGroupIssueDto,
      attachmentFile,
      req.user.id,
    );
  }

  @Delete(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) =>
    ability.can(Action.Delete, WORKING_GROUP_ISSUE_SUBJECT),
  )
  @ApiOperation({ summary: 'Soft delete a working group issue' })
  @ApiResponse({ status: 200, description: 'Working group issue deleted' })
  @ApiResponse({ status: 404, description: 'Working group issue not found' })
  remove(
    @Req() req: Request & { user: { id: number } },
    @Param('id', ParseIntPipe) id: number,
  ): Promise<MessageResponse> {
    return this.workingGroupIssuesService.remove(id, req.user.id);
  }
}
