import { SUPPORT_ATTACHMENT_RESOURCE } from "@edr/types";
import {
  Controller,
  ForbiddenException,
  Get,
  Param,
  ParseUUIDPipe,
  Query,
  Res,
} from "@nestjs/common";
import {
  ApiBearerAuth,
  ApiOperation,
  ApiQuery,
  ApiTags,
} from "@nestjs/swagger";
import { Response } from "express";

import { FilesService } from "./files.service";

@ApiTags("files")
@ApiBearerAuth()
@Controller("files")
export class FilesController {
  constructor(private readonly filesService: FilesService) {}

  @Get(":fileId")
  // Authenticated: no @Public, so the global JwtGuard applies. Unguessable file
  // UUIDs are obscurity, not authorization — raw byte streams must require auth.
  // Browser inline previews (<img>/<iframe>/<a>) that can't carry the Bearer
  // token should use a short-lived signed URL instead (FilesService.signUrl).
  // TODO: enforce ownership-by-resource here next (scope the file to the
  // caller's booking/company before streaming). Until that lands, any resource
  // whose files are cross-tenant sensitive must opt OUT of this route and expose
  // its own checked endpoint — see the support_message case below.
  @ApiOperation({
    summary: "Stream a file by ID",
    description:
      "Global endpoint — streams any uploaded file directly from MinIO by its UUID. " +
      "No resource context (e.g. booking ID) required. Serves inline by default so " +
      "the browser can preview it; pass ?download=1 to force a download. " +
      "Support-chat attachments are NOT served here — use GET /support/attachments/:fileId.",
  })
  @ApiQuery({
    name: "download",
    required: false,
    description: "Set to 1/true to force a download instead of inline preview.",
  })
  async download(
    @Param("fileId", ParseUUIDPipe) fileId: string,
    @Query("download") download: string | undefined,
    @Res() res: Response,
  ) {
    const record = await this.filesService.findById(fileId);

    // Chat attachments are cross-tenant sensitive and this route has no
    // ownership check, so a leaked/guessed UUID would hand one company's file to
    // another. SupportAttachmentController scopes the caller to the owning
    // thread; refuse here rather than quietly serving the bytes.
    if (record.resource === SUPPORT_ATTACHMENT_RESOURCE) {
      throw new ForbiddenException(
        "Support chat attachments must be fetched via GET /support/attachments/:fileId.",
      );
    }

    const { stream } = await this.filesService.streamById(fileId);
    const forceDownload = download === "1" || download === "true";
    const disposition = forceDownload ? "attachment" : "inline";

    res.setHeader("Content-Type", record.mimeType);
    res.setHeader(
      "Content-Disposition",
      `${disposition}; filename="${record.name}"`,
    );
    // Allow the browser to cache the streamed bytes briefly for smoother
    // in-page previews (re-opening the viewer shouldn't re-hit MinIO).
    res.setHeader("Cache-Control", "private, max-age=300");
    stream.pipe(res);
  }
}
