import {
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Request,
  Res,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import type { Response, Request as ExpressRequest } from 'express';
import { randomUUID } from 'crypto';
import { JwtAuthGuard } from '@modules/organization/auth/guards/jwt-auth.guard';
import { SkipTransform } from '@common/decorators/skip-transform.decorator';
import { RequirePermissions } from '@common/decorators/permissions.decorator';
import { handleMeetingAttendanceError } from '../errors/handle-controller-error';
import { AgendaItemAttachmentService } from '../services/agenda-item-attachment.service';
import { AgendaService } from '../services/agenda.service';
import { MeetingAccessService } from '../services/meeting-access.service';
import { agendaItemNotFound, attachmentTooLarge } from '../errors/agenda.error';
import { MAX_FILE_SIZE_BYTES, getTmpUploadDir } from '../constants/attachment';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { diskStorage } = require('multer');

interface MulterFile {
  fieldname: string;
  originalname: string;
  encoding: string;
  mimetype: string;
  destination: string;
  filename: string;
  path: string;
  size: number;
}

interface ReqUser {
  userId: string;
  id?: string;
  email?: string;
  permissions?: string[];
  organizationId?: string | null;
  roles?: any[];
  organizationRoles?: Record<string, string[]>;
  role?: string;
}

/**
 * 议程项级附件 v1.0 controller（[114] / [115] / [116]）。
 *
 * 上传 multer 配置：
 * - diskStorage（不用 memoryStorage，否则 200MB 文件 OOM）
 * - tmp dest = `${MEETING_ATTACHMENT_STORAGE_ROOT}/tmp/uploads/`（与正式存储同盘避免 EXDEV）
 * - filename = `<uuid>.tmp`（service 落正式位置时按 mime 选 ext）
 * - limits: fileSize 200MB / files 1 / fieldSize 10MB
 *
 * multer 抛 LIMIT_FILE_SIZE → 转 413 ATTACHMENT_TOO_LARGE。
 *
 * 注：路径 `/agenda-items/:itemId/attachments/:attachmentId` 的 DELETE 鉴权
 *     既允许 uploader 本人也允许 manager → 在 service 内部判定。
 */
@Controller('meeting-attendance/agenda-items/:itemId/attachments')
@SkipTransform()
@UseGuards(JwtAuthGuard)
export class MeetingAttendanceAgendaItemAttachmentController {
  constructor(
    private readonly attachmentService: AgendaItemAttachmentService,
    private readonly agendaService: AgendaService,
    private readonly accessService: MeetingAccessService,
  ) {}

  /** [114] 上传议程项级附件。 */
  @Post()
  // upload-task:any 或 upload-task:assigned 二选一即可；统一通过自定义校验
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: (
          _req: unknown,
          _file: MulterFile,
          cb: (e: Error | null, dest: string) => void,
        ) => cb(null, getTmpUploadDir()),
        filename: (
          _req: unknown,
          _file: MulterFile,
          cb: (e: Error | null, name: string) => void,
        ) => cb(null, `${randomUUID()}.tmp`),
      }),
      limits: { fileSize: MAX_FILE_SIZE_BYTES, files: 1, fieldSize: 10 * 1024 * 1024 },
    }),
  )
  async upload(
    @Param('itemId') itemId: string,
    @UploadedFile() file: MulterFile | undefined,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireActor(req, res);
      if (!actor) return res;

      if (!file) {
        return res.status(400).json({ error: 'No file uploaded' });
      }

      const meetingId = await this.agendaService.findMeetingIdByItem(itemId);
      if (!meetingId) throw agendaItemNotFound();
      // 必须先是参会人（含 manager / creator）
      if (!(await this.accessService.isAttendee(meetingId, actor))) {
        return res
          .status(403)
          .json({ code: 'MEETING_ATTENDANCE_011', error: 'Not in the meeting attendee list' });
      }

      const permissions = actor.permissions ?? [];
      const result = await this.attachmentService.upload(
        itemId,
        file,
        { userId: actor.userId, organizationId: actor.organizationId ?? null, permissions },
        req,
      );
      return res.status(201).json(result);
    } catch (error: any) {
      // multer LIMIT_FILE_SIZE → 转 413
      if (error?.code === 'LIMIT_FILE_SIZE') {
        const err = attachmentTooLarge();
        return res.status(err.status).json({ code: err.code, error: err.message });
      }
      return handleMeetingAttendanceError(res, error, 'Failed to upload attachment', 'MeetingAttendanceAgendaItemAttachmentController');
    }
  }

  /** [115] 列表议程项下附件。 */
  @Get()
  @RequirePermissions('meeting:agenda:read')
  async list(
    @Param('itemId') itemId: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireActor(req, res);
      if (!actor) return res;
      const meetingId = await this.agendaService.findMeetingIdByItem(itemId);
      if (!meetingId) throw agendaItemNotFound();
      if (!(await this.accessService.isAttendee(meetingId, actor))) {
        return res
          .status(403)
          .json({ code: 'MEETING_ATTENDANCE_011', error: 'Not in the meeting attendee list' });
      }
      const result = await this.attachmentService.listByItem(itemId);
      return res.status(200).json(result);
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to list attachments', 'MeetingAttendanceAgendaItemAttachmentController');
    }
  }

  /** [116] 软删议程项附件（uploader 本人或 manager）。 */
  @Delete(':attachmentId')
  async delete(
    @Param('itemId') itemId: string,
    @Param('attachmentId') attachmentId: string,
    @Request() req: ExpressRequest,
    @Res() res: Response,
  ) {
    try {
      const actor = this.requireActor(req, res);
      if (!actor) return res;
      const meetingId = await this.agendaService.findMeetingIdByItem(itemId);
      if (!meetingId) throw agendaItemNotFound();
      if (!(await this.accessService.isAttendee(meetingId, actor))) {
        return res
          .status(403)
          .json({ code: 'MEETING_ATTENDANCE_011', error: 'Not in the meeting attendee list' });
      }
      const isAdminOrManager = this.accessService.isAdminOrManager(actor);
      await this.attachmentService.deleteAttachment(
        itemId,
        attachmentId,
        {
          userId: actor.userId,
          organizationId: actor.organizationId ?? null,
          permissions: actor.permissions ?? [],
          isAdminOrManager,
        },
        req,
      );
      return res.status(204).send();
    } catch (error) {
      return handleMeetingAttendanceError(res, error, 'Failed to delete attachment', 'MeetingAttendanceAgendaItemAttachmentController');
    }
  }

  private requireActor(req: ExpressRequest, res: Response): ReqUser | null {
    const user = req.user as ReqUser | undefined;
    const userId = user?.userId ?? user?.id;
    if (!userId) {
      res.status(401).json({ error: 'Unauthorized' });
      return null;
    }
    return { ...user, userId } as ReqUser;
  }
}
