/**
 * Route handler for `GET /api/v1/documents/[id]/download/[...slug]`.
 *
 * Lives in handlers.ts (not route.ts) so integration tests can call it
 * directly, bypassing auth/permission middleware (per CLAUDE.md "Route
 * handler exports" convention).
 *
 * Lookup is keyed off the doc id; the slug embeds the current folder path +
 * filename so a forwarded link reads like `Deals 2026/Q1/contract.pdf` even
 * though the underlying storage key is a UUID. The slug is rebuilt from
 * current state and compared with the supplied path - a stale or
 * hand-edited URL 404s rather than silently serving the wrong file.
 */

import { Readable } from 'node:stream';

import { NextResponse } from 'next/server';
import { and, eq } from 'drizzle-orm';

import { type RouteHandler } from '@/lib/api/helpers';
import { db } from '@/lib/db';
import { documents, files } from '@/lib/db/schema/documents';
import { errorResponse, NotFoundError } from '@/lib/errors';
import { getStorageBackend } from '@/lib/storage';
import { listTree, type FolderNode } from '@/lib/services/document-folders.service';
import { findFolderPath } from '@/lib/services/documents.service';

type DownloadParams = { id: string; slug: string[] };

export const downloadHandler: RouteHandler<DownloadParams> = async (_req, ctx, params) => {
  try {
    const docId = params.id;
    if (!docId) throw new NotFoundError('Document');

    const doc = await db
      .select({
        id: documents.id,
        folderId: documents.folderId,
        fileId: documents.fileId,
        fileStoragePath: files.storagePath,
        fileMimeType: files.mimeType,
        fileFilename: files.filename,
      })
      .from(documents)
      .leftJoin(files, eq(files.id, documents.fileId))
      .where(and(eq(documents.id, docId), eq(documents.portId, ctx.portId)))
      .limit(1)
      .then((r) => r[0]);

    if (!doc) throw new NotFoundError('Document');
    if (!doc.fileStoragePath || !doc.fileFilename) {
      throw new NotFoundError('Document file');
    }

    const tree = await listTree(ctx.portId);
    const expected = buildExpectedSlug(doc.folderId, doc.fileFilename, tree);
    const supplied = (params.slug ?? []).map(decodeURIComponent).join('/');
    if (supplied !== expected) throw new NotFoundError('Document at this path');

    const backend = await getStorageBackend();
    const nodeStream = await backend.get(doc.fileStoragePath);
    const webStream = Readable.toWeb(nodeStream as Readable) as ReadableStream;

    return new NextResponse(webStream, {
      status: 200,
      headers: {
        'content-type': doc.fileMimeType ?? 'application/octet-stream',
        'content-disposition': `inline; filename="${sanitizeFilenameForHeader(doc.fileFilename)}"`,
      },
    });
  } catch (error) {
    return errorResponse(error);
  }
};

function buildExpectedSlug(folderId: string | null, filename: string, tree: FolderNode[]): string {
  const segments: string[] = [];
  if (folderId) {
    const path = findFolderPath(tree, folderId);
    for (const node of path) segments.push(node.name);
  }
  segments.push(filename);
  return segments.join('/');
}

/**
 * Strip control + quote characters from a filename before placing it in the
 * Content-Disposition header value. The legacy `inline; filename="..."` form
 * is what the rest of the CRM emits today; this is the same minimal
 * sanitization the other download endpoints rely on.
 */
function sanitizeFilenameForHeader(filename: string): string {
  return filename.replace(/["\\\r\n]/g, '_');
}
