// src/certificates/bulk-scan/bulk-scan.controller.ts
import {
  Controller,
  Post,
  UploadedFiles,
  UseInterceptors,
  UseFilters,
  BadRequestException,
  HttpException,
  HttpStatus,
  Logger,
  Req,
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { BulkScanService } from './bulk-scan.service';
import { MulterExceptionFilter } from './multer-exception.filter';
import type { Request } from 'express';

// ─── Tunables ────────────────────────────────────────────────────────────────
// Bumped from 50 → 200. The frontend hint says "up to 50" but the real bug
// the user hit was uploading 66 files. 200 is a safe ceiling for one POST;
// if you ever need more, chunk on the frontend instead.
const MAX_FILES_PER_REQUEST = 200;
const MAX_FILE_SIZE_BYTES = 25 * 1024 * 1024; // 25 MB / file
const MAX_TOTAL_BYTES = 500 * 1024 * 1024;     // 500 MB / request — guard rail

// We accept by extension AND mimetype. Some browsers send a wrong mimetype
// for .pdf (e.g. "application/octet-stream"), so extension is the safer check.
const ACCEPTED_EXTENSIONS = ['.pdf'];
const ACCEPTED_MIMETYPES = ['application/pdf'];

@Controller('certificates/bulk-scan')
@UseFilters(new MulterExceptionFilter()) // ← turns ugly multer errors into clear messages
export class BulkScanController {
  private readonly logger = new Logger(BulkScanController.name);

  constructor(private readonly bulkScanService: BulkScanService) {}

  /**
   * POST /api/certificates/bulk-scan/upload
   *
   * multipart/form-data, field name = "files" (array of PDFs)
   *
   * Returns: { matched, review, unmatched, summary }
   *
   * Robustness:
   *  - Up to 200 files per request (was 50 — caused the bug)
   *  - Non-PDFs are SOFT-skipped and reported in `unmatched`, not thrown
   *  - Multer errors translated by MulterExceptionFilter
   *  - Service errors caught & wrapped (no opaque 500s)
   *  - Defensive null-checks throughout
   *  - Full request logging for production debugging
   */
  @Post('upload')
  @UseInterceptors(
    FilesInterceptor('files', MAX_FILES_PER_REQUEST, {
      limits: {
        fileSize: MAX_FILE_SIZE_BYTES,
        files: MAX_FILES_PER_REQUEST,
        // Reject pathological multipart bombs early
        fieldNameSize: 200,
        fields: 20,
      },
      fileFilter: (req: any, file, cb) => {
        try {
          const lower = (file.originalname || '').toLowerCase();
          const extOk = ACCEPTED_EXTENSIONS.some((ext) => lower.endsWith(ext));
          const mimeOk = ACCEPTED_MIMETYPES.includes(file.mimetype);

          if (!extOk && !mimeOk) {
            // ✅ SOFT-SKIP — track on req so we can report it back to the
            // frontend, but DON'T abort the whole upload like the old code did.
            if (!req._skippedFiles) req._skippedFiles = [];
            req._skippedFiles.push({
              filename: file.originalname || 'unknown',
              reason: `Not a PDF (mimetype: ${file.mimetype || 'unknown'})`,
            });
            return cb(null, false);
          }
          cb(null, true);
        } catch (err: any) {
          // Never let the filter throw — that aborts the whole upload
          if (!req._skippedFiles) req._skippedFiles = [];
          req._skippedFiles.push({
            filename: file?.originalname || 'unknown',
            reason: `File rejected: ${err?.message || 'unknown error'}`,
          });
          cb(null, false);
        }
      },
    }),
  )
  async uploadBulkScans(
    @UploadedFiles() files: Express.Multer.File[],
    @Req() req: Request,
  ) {
    // ── Defensive normalization ────────────────────────────────────────────
    const safeFiles: Express.Multer.File[] = Array.isArray(files) ? files : [];
    const skipped: { filename: string; reason: string }[] =
      (req as any)._skippedFiles ?? [];

    const totalBytes = safeFiles.reduce((sum, f) => sum + (f.size || 0), 0);

    this.logger.log(
      `📥 Bulk-scan upload — accepted: ${safeFiles.length}, ` +
        `skipped: ${skipped.length}, total: ${(totalBytes / 1024 / 1024).toFixed(1)} MB`,
    );

    // ── Total-size guard rail ──────────────────────────────────────────────
    if (totalBytes > MAX_TOTAL_BYTES) {
      this.logger.warn(
        `⚠️ Upload exceeds total size limit: ${totalBytes} > ${MAX_TOTAL_BYTES}`,
      );
      throw new HttpException(
        {
          message: 'Upload too large',
          detail: `Total size ${(totalBytes / 1024 / 1024).toFixed(1)} MB exceeds the ${MAX_TOTAL_BYTES / 1024 / 1024} MB limit. Try uploading in smaller batches.`,
        },
        HttpStatus.PAYLOAD_TOO_LARGE,
      );
    }

    // ── Empty-batch guard ──────────────────────────────────────────────────
    if (safeFiles.length === 0 && skipped.length === 0) {
      throw new BadRequestException({
        message: 'No files received',
        hint:
          'Send PDFs as multipart/form-data with field name "files". ' +
          'Make sure your FormData uses fd.append("files", file) for each file.',
      });
    }

    if (safeFiles.length === 0 && skipped.length > 0) {
      // Every file was filtered — return the rejections so the user knows why
      return {
        matched: [],
        review: [],
        unmatched: skipped,
        summary: {
          total: skipped.length,
          matched: 0,
          review: 0,
          unmatched: skipped.length,
        },
      };
    }

    // ── Pull userId from JWT (works whether the guard sets sub / id / userId) ──
    const user = (req as any).user;
    const userId =
      typeof user === 'object' && user !== null
        ? user.id ?? user.sub ?? user.userId
        : undefined;

    // ── Process — wrap in try/catch so DB / FS errors don't leak ───────────
    try {
      const result = await this.bulkScanService.processBulkUpload(
        safeFiles,
        userId,
      );

      // Merge soft-skipped files into the unmatched bucket so the UI shows them
      if (skipped.length > 0) {
        result.unmatched = [...(result.unmatched ?? []), ...skipped];
        result.summary = {
          ...result.summary,
          total: (result.summary?.total ?? 0) + skipped.length,
          unmatched: (result.summary?.unmatched ?? 0) + skipped.length,
        };
      }

      this.logger.log(
        `✅ Bulk-scan done — matched: ${result.summary.matched}, ` +
          `review: ${result.summary.review}, unmatched: ${result.summary.unmatched}`,
      );

      return result;
    } catch (err: any) {
      this.logger.error(
        `❌ Bulk-scan processing failed: ${err?.message ?? err}`,
        err?.stack,
      );

      // Re-throw HttpExceptions as-is, wrap everything else
      if (err instanceof HttpException) throw err;

      throw new HttpException(
        {
          message: 'Bulk-scan processing failed',
          detail: err?.message ?? 'Unknown error',
          hint:
            'Check server logs for the full stack trace. ' +
            'If this keeps happening, retry with a smaller batch.',
        },
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}