import {
  BadRequestException,
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Query,
  Req,
  UnauthorizedException,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import type { Request } from 'express';
import { randomUUID } from 'node:crypto';
import { mkdirSync } from 'node:fs';
import { extname, join } from 'node:path';
import { diskStorage } from 'multer';

import { JwtAuthGuard } from '@/modules/auth/jwt-auth.guard';

import { CreatePlenaryDto } from './dto/create-plenary.dto';
import { PlenaryQueryDto } from './dto/plenary-query.dto';
import { UpdatePlenaryDto } from './dto/update-plenary.dto';
import { PlenaryService } from './plenary.service';

const MAX_PDF_SIZE = 10 * 1024 * 1024;
const PLENARY_DOCUMENT_DIRECTORY = join(process.cwd(), 'uploads', 'plenaries');

mkdirSync(PLENARY_DOCUMENT_DIRECTORY, { recursive: true });

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

@ApiTags('plenaries')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard)
@Controller('plenaries')
export class PlenaryController {
  constructor(private readonly plenaryService: PlenaryService) {}

  @Get('lookups/ministries')
  getMinistries(@Query('relatedStakeholderId') relatedStakeholderId?: string) {
    return this.plenaryService.getMinistries(
      relatedStakeholderId ? Number(relatedStakeholderId) : undefined,
    );
  }

  @Get()
  findAll(
    @Query() query: PlenaryQueryDto,
    @Req() request: AuthenticatedRequest,
  ) {
    const userId =
      query.ministryOnly === true
        ? this.getAuthenticatedUserId(request)
        : undefined;

    return this.plenaryService.findAll(query, userId);
  }

  @Post()
  create(@Body() dto: CreatePlenaryDto, @Req() request: AuthenticatedRequest) {
    return this.plenaryService.create(
      dto,
      this.getAuthenticatedUserId(request),
    );
  }

  @Get(':id')
  findOne(
    @Param('id', ParseIntPipe) plenaryId: number,
    @Req() request: AuthenticatedRequest,
  ) {
    return this.plenaryService.findOne(
      plenaryId,
      this.getAuthenticatedUserId(request),
    );
  }

  @Patch(':id/document')
  @HttpCode(HttpStatus.OK)
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: PLENARY_DOCUMENT_DIRECTORY,
        filename: (_request, file, callback) => {
          callback(
            null,
            `${Date.now()}-${randomUUID()}${extname(file.originalname).toLowerCase() || '.pdf'}`,
          );
        },
      }),
      limits: { fileSize: MAX_PDF_SIZE },
      fileFilter: (_request, file, callback) => {
        const isPdf =
          file.mimetype === 'application/pdf' ||
          extname(file.originalname).toLowerCase() === '.pdf';

        if (!isPdf) {
          callback(
            new BadRequestException('Only PDF files are allowed.'),
            false,
          );
          return;
        }

        callback(null, true);
      },
    }),
  )
  updateDocument(
    @Param('id', ParseIntPipe) plenaryId: number,
    @UploadedFile() file?: Express.Multer.File,
  ) {
    if (!file)
      throw new BadRequestException('Please select a PDF document file.');

    const originalName = file.originalname.trim() || file.filename;
    const documentReference = `/uploads/plenaries/${file.filename}?originalName=${encodeURIComponent(originalName)}`;

    return this.plenaryService.updateDocument(plenaryId, documentReference);
  }

  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) plenaryId: number,
    @Body() dto: UpdatePlenaryDto,
  ) {
    return this.plenaryService.update(plenaryId, dto);
  }

  @Patch(':id/submit')
  submitToCdc(@Param('id', ParseIntPipe) plenaryId: number) {
    return this.plenaryService.submitToCdc(plenaryId);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.OK)
  remove(@Param('id', ParseIntPipe) plenaryId: number) {
    return this.plenaryService.remove(plenaryId);
  }

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

    if (!Number.isInteger(userId) || Number(userId) <= 0)
      throw new UnauthorizedException('Authenticated user is required.');

    return Number(userId);
  }
}
