import {
  Body,
  Controller,
  DefaultValuePipe,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  ParseUUIDPipe,
  Patch,
  Post,
  Query,
  Res,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiConsumes,
  ApiOperation,
  ApiResponse,
  ApiTags,
} from '@nestjs/swagger';
import type { Response } from 'express';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import type { PublicUser } from '../user/user.service';
import {
  TaskService,
  type AttachmentResponse,
  type CommentReplyResponse,
  type CommentResponse,
  type ProjectTaskListResult,
  type TaskDetailResponse,
} from './task.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
import { CheckpointReportDto } from './dto/checkpoint-report.dto';
import { ChangeStatusDto } from './dto/change-status.dto';
import { CreateCommentDto, CreateReplyDto } from './dto/create-comment.dto';
import { ApprovalChangesDto, ApprovalRequestDto } from './dto/approval.dto';
import {
  MAX_FILE_SIZE,
  UPLOAD_DIR,
  fileMimeFilter,
  type UploadedDiskFile,
} from './upload.config';
import { BusinessException } from '../../common/exceptions/business.exception';

// 업무 컨트롤러 — 모든 엔드포인트 인증 필요 (projectId = slugName, taskId = repo 내 순번)
@ApiTags('Task')
@Controller('projects/:projectId/tasks')
@UseGuards(JwtAuthGuard)
export class TaskController {
  constructor(private readonly taskService: TaskService) {}

  @Get()
  @ApiOperation({
    summary: '업무 목록 (세그먼트/검색 + 페이지네이션 + 세그먼트 카운트)',
  })
  @ApiResponse({ status: 200, description: '목록 조회 성공' })
  @ApiResponse({
    status: 404,
    description: '프로젝트를 찾을 수 없음 (RES_001)',
  })
  list(
    @Param('projectId') projectId: string,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('size', new DefaultValuePipe(20), ParseIntPipe) size: number,
    @Query('segment') segment?: string,
    @Query('q') q?: string,
    @Query('assignee') assignee?: string,
    @Query('sort') sort?: string,
  ): Promise<ProjectTaskListResult> {
    return this.taskService.list(
      projectId,
      { segment, q, assignee, sort },
      page,
      size,
    );
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: '업무 생성 (admin)' })
  @ApiResponse({ status: 201, description: '생성 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  @ApiResponse({
    status: 400,
    description: '담당자가 프로젝트 멤버가 아님 (BIZ_001)',
  })
  create(
    @Param('projectId') projectId: string,
    @Body() dto: CreateTaskDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.create(projectId, user.id, dto);
  }

  @Get(':taskId')
  @ApiOperation({ summary: '업무 상세' })
  @ApiResponse({ status: 200, description: '조회 성공' })
  @ApiResponse({ status: 404, description: '업무를 찾을 수 없음 (RES_001)' })
  findOne(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
  ): Promise<TaskDetailResponse> {
    return this.taskService.findOne(projectId, taskId);
  }

  @Patch(':taskId')
  @ApiOperation({ summary: '업무 수정 (admin)' })
  @ApiResponse({ status: 200, description: '수정 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  update(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Body() dto: UpdateTaskDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.update(projectId, user.id, taskId, dto);
  }

  @Delete(':taskId')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '업무 삭제 (admin)' })
  @ApiResponse({ status: 200, description: '삭제 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  async remove(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @CurrentUser() user: PublicUser,
  ): Promise<null> {
    await this.taskService.remove(projectId, user.id, taskId);
    return null;
  }

  @Post(':taskId/status')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '업무 상태 변경 (상태 머신)' })
  @ApiResponse({ status: 200, description: '변경 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  @ApiResponse({
    status: 409,
    description: '허용되지 않는 상태 전이 (BIZ_001)',
  })
  changeStatus(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Body() dto: ChangeStatusDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.changeStatus(
      projectId,
      user.id,
      taskId,
      dto.status,
    );
  }

  /* ===================== 중간 점검(체크포인트) ===================== */

  @Post(':taskId/checkpoints/:checkpointId/report')
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: '중간 점검 보고 (담당자) — 점검일 당일에만' })
  @ApiResponse({ status: 201, description: '보고 접수' })
  @ApiResponse({ status: 403, description: '담당자 아님 (AUTH_003)' })
  @ApiResponse({ status: 404, description: '점검일을 찾을 수 없음 (RES_001)' })
  submitCheckpointReport(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Param('checkpointId', ParseUUIDPipe) checkpointId: string,
    @Body() dto: CheckpointReportDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.submitCheckpointReport(
      projectId,
      user.id,
      taskId,
      checkpointId,
      dto,
    );
  }

  /* ===================== 댓글 / 답글 (5단계) ===================== */

  @Post(':taskId/comments')
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: '댓글 작성 (멤버)' })
  @ApiResponse({ status: 201, description: '작성 성공' })
  addComment(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Body() dto: CreateCommentDto,
    @CurrentUser() user: PublicUser,
  ): Promise<CommentResponse> {
    return this.taskService.addComment(
      projectId,
      user.id,
      taskId,
      dto.text,
      dto.fileId,
    );
  }

  @Post(':taskId/comments/:commentId/replies')
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: '답글 작성 (멤버)' })
  @ApiResponse({ status: 201, description: '작성 성공' })
  addReply(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Param('commentId', ParseUUIDPipe) commentId: string,
    @Body() dto: CreateReplyDto,
    @CurrentUser() user: PublicUser,
  ): Promise<CommentReplyResponse> {
    return this.taskService.addReply(
      projectId,
      user.id,
      taskId,
      commentId,
      dto.text,
    );
  }

  /* ===================== 파일 첨부 (5단계) ===================== */

  @Post(':taskId/files')
  @HttpCode(HttpStatus.CREATED)
  @UseInterceptors(
    FileInterceptor('file', {
      dest: UPLOAD_DIR,
      limits: { fileSize: MAX_FILE_SIZE },
      fileFilter: fileMimeFilter,
    }),
  )
  @ApiConsumes('multipart/form-data')
  @ApiOperation({ summary: '파일 업로드 (멤버)' })
  @ApiResponse({ status: 201, description: '업로드 성공' })
  addFile(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @UploadedFile() file: UploadedDiskFile,
    @CurrentUser() user: PublicUser,
  ): Promise<AttachmentResponse> {
    // 파일 누락 또는 허용되지 않는 형식(fileFilter 가 걸러 file 미존재)
    if (!file) {
      throw new BusinessException(
        'BIZ_001',
        '업로드할 수 없는 파일 형식이거나 파일이 없습니다.',
        HttpStatus.BAD_REQUEST,
      );
    }
    return this.taskService.addFile(projectId, user.id, taskId, file);
  }

  @Delete(':taskId/files/:fileId')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '파일 삭제 (멤버)' })
  @ApiResponse({ status: 200, description: '삭제 성공' })
  async removeFile(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Param('fileId', ParseUUIDPipe) fileId: string,
    @CurrentUser() user: PublicUser,
  ): Promise<null> {
    await this.taskService.removeFile(projectId, user.id, taskId, fileId);
    return null;
  }

  @Get(':taskId/files/:fileId/download')
  @ApiOperation({ summary: '파일 다운로드 (멤버)' })
  @ApiResponse({ status: 200, description: '파일 스트림' })
  async downloadFile(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Param('fileId', ParseUUIDPipe) fileId: string,
    @CurrentUser() user: PublicUser,
    @Res() res: Response,
  ): Promise<void> {
    const { attachment, absolutePath } =
      await this.taskService.getFileForDownload(
        projectId,
        user.id,
        taskId,
        fileId,
      );
    // 표준 응답 래퍼를 거치지 않고 바이너리를 직접 스트리밍(@Res).
    // attachment(다운로드 강제) + nosniff 로 브라우저 인라인 렌더링/스니핑 차단(저장형 XSS 방지)
    res.setHeader('Content-Type', attachment.mime);
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader(
      'Content-Disposition',
      `attachment; filename*=UTF-8''${encodeURIComponent(attachment.name)}`,
    );
    res.sendFile(absolutePath);
  }

  /* ===================== 승인 워크플로우 (5단계) ===================== */

  @Post(':taskId/approval/request')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '승인 요청 (담당자 → 지시자)' })
  @ApiResponse({ status: 200, description: '요청 성공' })
  @ApiResponse({ status: 409, description: '허용되지 않는 전이 (BIZ_001)' })
  requestApproval(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Body() dto: ApprovalRequestDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.requestApproval(
      projectId,
      user.id,
      taskId,
      dto.note,
    );
  }

  @Post(':taskId/approval/approve')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '승인 (지시자/admin)' })
  @ApiResponse({ status: 200, description: '승인 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  approve(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.approve(projectId, user.id, taskId);
  }

  @Post(':taskId/approval/changes')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '수정 요청 (지시자/admin)' })
  @ApiResponse({ status: 200, description: '요청 성공' })
  @ApiResponse({ status: 403, description: '권한 없음 (AUTH_003)' })
  requestChanges(
    @Param('projectId') projectId: string,
    @Param('taskId', ParseIntPipe) taskId: number,
    @Body() dto: ApprovalChangesDto,
    @CurrentUser() user: PublicUser,
  ): Promise<TaskDetailResponse> {
    return this.taskService.requestChanges(
      projectId,
      user.id,
      taskId,
      dto.note,
      dto.checklist,
    );
  }
}
