import { NextResponse } from 'next/server';
import { z } from 'zod';

import { withAuth, withPermission } from '@/lib/api/helpers';
import { errorResponse, ForbiddenError, NotFoundError, ValidationError } from '@/lib/errors';
import {
  uploadDocumentForSigning,
  type CustomDocumentType,
  type CustomRecipientRole,
} from '@/lib/services/custom-document-upload.service';
import { isPdfMagic } from '@/lib/services/berth-pdf-parser';

/**
 * Phase 3 - Custom document upload-to-Documenso endpoint.
 *
 * POST `/api/v1/interests/[id]/upload-for-signing`
 *
 * Body: multipart/form-data
 *   - file:        the source PDF (browser-supplied; magic-byte verified)
 *   - documentType: 'contract' | 'reservation_agreement'
 *   - title:       customer-visible document title
 *   - recipients:  JSON-encoded CustomDocumentRecipient[]
 *   - fields:      JSON-encoded field placement array
 *
 * The Contract + Reservation tabs (Phase 4) post here from their
 * drag-drop UI. Tests can invoke the service directly.
 *
 * Permission: documents.send_for_signing - sending a document for
 * signing is destructive (queues an outbound email + an admin-visible
 * Documenso doc). Plus interests.edit because the pipeline-stage
 * auto-advance side-effect is interest-mutating (matches the
 * external-eoi precedent).
 */

const recipientSchema = z.object({
  name: z.string().min(1).max(200),
  email: z.string().email(),
  role: z.enum(['SIGNER', 'APPROVER', 'CC']),
  signingOrder: z.number().int().positive(),
});

const fieldSchema = z.object({
  recipientIndex: z.number().int().nonnegative(),
  type: z.enum([
    'SIGNATURE',
    'FREE_SIGNATURE',
    'INITIALS',
    'DATE',
    'EMAIL',
    'NAME',
    'TEXT',
    'NUMBER',
    'CHECKBOX',
    'DROPDOWN',
    'RADIO',
  ]),
  pageNumber: z.number().int().positive(),
  pageX: z.number().min(0).max(100),
  pageY: z.number().min(0).max(100),
  pageWidth: z.number().positive().max(100),
  pageHeight: z.number().positive().max(100),
  fieldMeta: z.record(z.string(), z.unknown()).optional(),
});

const documentTypeSchema = z.enum(['eoi', 'contract', 'reservation_agreement', 'generic']);

const MAX_PDF_BYTES = 50 * 1024 * 1024;

function parseJsonField<T>(raw: unknown, schema: z.ZodType<T>, label: string): T {
  if (typeof raw !== 'string') {
    throw new ValidationError(`Missing or non-string '${label}' field`);
  }
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new ValidationError(`'${label}' is not valid JSON`);
  }
  const result = schema.safeParse(parsed);
  if (!result.success) {
    throw new ValidationError(`'${label}' validation failed: ${result.error.issues[0]?.message}`);
  }
  return result.data;
}

export const POST = withAuth(
  withPermission('documents', 'send_for_signing', async (req, ctx, params) => {
    try {
      const interestId = params.id;
      if (!interestId) throw new NotFoundError('Interest');

      if (!ctx.isSuperAdmin && !ctx.permissions?.interests?.edit) {
        throw new ForbiddenError('interests.edit required to upload a document for signing');
      }

      const form = await req.formData();

      // ─── file ──────────────────────────────────────────────────
      const file = form.get('file');
      if (!file || !(file instanceof File)) {
        throw new ValidationError('Missing file');
      }
      if (file.size > MAX_PDF_BYTES) {
        throw new ValidationError(`File exceeds ${MAX_PDF_BYTES / 1024 / 1024} MB cap`);
      }
      const buffer = Buffer.from(await file.arrayBuffer());
      // Magic-byte check at the route boundary too - service repeats it
      // as defense in depth but a bad upload should error before we hit
      // any side-effecting code.
      if (!isPdfMagic(buffer)) {
        throw new ValidationError('Uploaded file is not a PDF');
      }

      // ─── scalar fields ─────────────────────────────────────────
      const documentType = documentTypeSchema.parse(form.get('documentType')) as CustomDocumentType;
      const title = z.string().min(1).max(255).parse(form.get('title'));
      const invitationMessageRaw = form.get('invitationMessage');
      const invitationMessage =
        typeof invitationMessageRaw === 'string'
          ? z.string().max(1000).parse(invitationMessageRaw)
          : null;

      // ─── JSON fields ───────────────────────────────────────────
      const recipients = parseJsonField(
        form.get('recipients'),
        z.array(recipientSchema).min(1).max(20),
        'recipients',
      );
      const fields = parseJsonField(
        form.get('fields'),
        z.array(fieldSchema).min(1).max(200),
        'fields',
      );

      const result = await uploadDocumentForSigning({
        interestId,
        portId: ctx.portId,
        portSlug: ctx.portSlug,
        documentType,
        title,
        pdfBuffer: buffer,
        filename: file.name || `${documentType}.pdf`,
        recipients: recipients.map((r) => ({
          name: r.name,
          email: r.email,
          role: r.role as CustomRecipientRole,
          signingOrder: r.signingOrder,
        })),
        fields,
        invitationMessage,
        meta: {
          userId: ctx.userId,
          portId: ctx.portId,
          ipAddress: ctx.ipAddress,
          userAgent: ctx.userAgent,
        },
      });

      return NextResponse.json({ data: result }, { status: 201 });
    } catch (error) {
      return errorResponse(error);
    }
  }),
);
