/**
 * External EOI upload — for EOIs signed outside Documenso (paper signing,
 * different e-sign vendor, signed in person, etc).
 *
 * Creates BOTH the document row AND the signed-file record in one shot,
 * then advances the interest stage. Distinct from the existing
 * uploadSignedManually flow which augments a document row that was
 * already created via the Documenso pathway.
 */

import { eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { interests } from '@/lib/db/schema/interests';
import { documents, documentEvents, files } from '@/lib/db/schema/documents';
import { ports } from '@/lib/db/schema/ports';
import { env } from '@/lib/env';
import { buildStoragePath } from '@/lib/minio';
import { getStorageBackend } from '@/lib/storage';
import { createAuditLog, type AuditMeta } from '@/lib/audit';
import { CodedError, NotFoundError, ValidationError } from '@/lib/errors';
import { emitToRoom } from '@/lib/socket/server';

export interface ExternalEoiInput {
  interestId: string;
  portId: string;
  /** PDF bytes. */
  fileData: { buffer: Buffer; originalName: string; mimeType: string; size: number };
  /** Free-text title for the document row. Defaults to "External EOI - <date>". */
  title?: string;
  /** When the signing actually happened (the date on the paper / contract). */
  signedAt?: Date;
  /** Names of the people who signed (free-text — we don't manage signer
   *  identities for external sigs). Recorded in metadata. */
  signerNames?: string[];
  /** Free-text note (e.g. "signed in person at boat show"). */
  notes?: string;
  meta: AuditMeta;
}

export async function uploadExternallySignedEoi(input: ExternalEoiInput) {
  const { interestId, portId, fileData, meta } = input;

  if (fileData.size <= 0) throw new ValidationError('Empty file');
  if (fileData.size > 25 * 1024 * 1024) {
    throw new ValidationError('File too large (max 25 MB)');
  }
  if (
    fileData.mimeType !== 'application/pdf' &&
    !fileData.originalName.toLowerCase().endsWith('.pdf')
  ) {
    throw new ValidationError('Only PDF uploads are accepted for signed EOIs');
  }

  const interest = await db.query.interests.findFirst({
    where: eq(interests.id, interestId),
  });
  if (!interest || interest.portId !== portId) throw new NotFoundError('Interest');

  const port = await db.query.ports.findFirst({ where: eq(ports.id, portId) });
  if (!port) throw new NotFoundError('Port');

  const documentId = crypto.randomUUID();
  const fileId = crypto.randomUUID();
  const storagePath = buildStoragePath(port.slug, 'eoi-signed', documentId, fileId, 'pdf');

  // Upload to storage FIRST so we have a stable key for the DB rows,
  // then commit all four DB writes in one transaction. If the tx fails
  // the storage object becomes orphaned (S3 isn't transactional) but
  // the DB stays clean — orphan reaper handles those.
  await (
    await getStorageBackend()
  ).put(storagePath, fileData.buffer, {
    contentType: 'application/pdf',
    sizeBytes: fileData.size,
  });

  const title =
    input.title ?? `External EOI — ${(input.signedAt ?? new Date()).toISOString().slice(0, 10)}`;

  const result = await db.transaction(async (tx) => {
    const [fileRecord] = await tx
      .insert(files)
      .values({
        portId,
        clientId: interest.clientId,
        filename: fileData.originalName,
        originalName: fileData.originalName,
        mimeType: 'application/pdf',
        sizeBytes: String(fileData.size),
        storagePath,
        storageBucket: env.MINIO_BUCKET,
        category: 'eoi',
        uploadedBy: meta.userId,
      })
      .returning();
    if (!fileRecord) {
      throw new CodedError('INSERT_RETURNING_EMPTY', {
        internalMessage: 'External EOI file insert returned no row',
      });
    }

    const [doc] = await tx
      .insert(documents)
      .values({
        id: documentId,
        portId,
        interestId,
        clientId: interest.clientId,
        yachtId: interest.yachtId,
        documentType: 'eoi',
        title,
        status: 'completed',
        isManualUpload: true,
        signedFileId: fileRecord.id,
        notes: input.notes ?? null,
        createdBy: meta.userId,
      })
      .returning();
    if (!doc) {
      throw new CodedError('INSERT_RETURNING_EMPTY', {
        internalMessage: 'External EOI document insert returned no row',
      });
    }

    await tx.insert(documentEvents).values({
      documentId: doc.id,
      eventType: 'completed',
      eventData: {
        isManualUpload: true,
        external: true,
        signerNames: input.signerNames ?? [],
        signedAt: (input.signedAt ?? new Date()).toISOString(),
        fileId: fileRecord.id,
      },
    });

    // Advance the interest stage to eoi_signed (no-op if already past it).
    // We bypass canTransitionStage explicitly because the operator just
    // brought concrete proof that the EOI is signed — that's higher
    // confidence than a normal forward-jump.
    if (
      interest.pipelineStage === 'open' ||
      interest.pipelineStage === 'details_sent' ||
      interest.pipelineStage === 'in_communication' ||
      interest.pipelineStage === 'eoi_sent'
    ) {
      await tx
        .update(interests)
        .set({
          pipelineStage: 'eoi_signed',
          eoiStatus: 'signed',
          dateEoiSigned: input.signedAt ?? new Date(),
          updatedAt: new Date(),
        })
        .where(eq(interests.id, interestId));
    } else {
      // Past eoi_signed — just record the document, don't touch stage.
      await tx.update(interests).set({ updatedAt: new Date() }).where(eq(interests.id, interestId));
    }

    return { documentId: doc.id, fileId: fileRecord.id };
  });

  const { documentId: docId, fileId: fId } = result;

  void createAuditLog({
    portId,
    userId: meta.userId,
    action: 'create',
    entityType: 'document',
    entityId: docId,
    metadata: {
      kind: 'external_eoi_upload',
      interestId,
      title,
      signerNames: input.signerNames ?? [],
      signedAt: (input.signedAt ?? new Date()).toISOString(),
      fileSizeBytes: fileData.size,
    },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  emitToRoom(`port:${portId}`, 'document:completed', { documentId: docId });

  return { documentId: docId, fileId: fId };
}
