/**
 * "Mark as signed externally" service - records that a contract /
 * reservation / EOI was signed outside the CRM without requiring the
 * rep to upload a file. The interest's doc-status flips to 'signed'
 * and an audit-log entry captures the optional reason ("paper signed
 * in-office", "scanned and emailed later", etc.).
 *
 * Distinct from `uploadExternallySignedEoi` which requires a PDF.
 * Reps reach for this when the original lives elsewhere (a physical
 * folder, the client's email, another system) and they don't want to
 * duplicate-store it just to flip the CRM forward.
 *
 * The UI surfaces a warning banner on the relevant tab: "No file on
 * record - signed externally". Reps can later upload the file via
 * the existing external-upload dialog if a copy turns up.
 */
import { and, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { interests } from '@/lib/db/schema/interests';
import { createAuditLog, type AuditMeta } from '@/lib/audit';
import { NotFoundError, ValidationError } from '@/lib/errors';
import { evaluateRule } from '@/lib/services/berth-rules-engine';
import { emitToRoom } from '@/lib/socket/server';

export type ExternalSignedDocType = 'eoi' | 'reservation' | 'contract';

const STATUS_COLUMNS: Record<
  ExternalSignedDocType,
  'eoiDocStatus' | 'reservationDocStatus' | 'contractDocStatus'
> = {
  eoi: 'eoiDocStatus',
  reservation: 'reservationDocStatus',
  contract: 'contractDocStatus',
};

const BERTH_RULE_TRIGGERS: Record<ExternalSignedDocType, 'eoi_signed' | 'contract_signed' | null> =
  {
    eoi: 'eoi_signed',
    reservation: null,
    contract: 'contract_signed',
  };

export interface MarkExternallySignedInput {
  interestId: string;
  portId: string;
  docType: ExternalSignedDocType;
  reason?: string | null;
}

export async function markExternallySigned(
  input: MarkExternallySignedInput,
  meta: AuditMeta,
): Promise<{ docType: ExternalSignedDocType; previousStatus: string | null }> {
  const interest = await db.query.interests.findFirst({
    where: and(eq(interests.id, input.interestId), eq(interests.portId, input.portId)),
  });
  if (!interest) throw new NotFoundError('Interest');

  const column = STATUS_COLUMNS[input.docType];
  const previousStatus = (interest[column] ?? null) as string | null;
  if (previousStatus === 'signed') {
    throw new ValidationError(
      `${input.docType} is already marked as signed for this interest. Use the upload flow to attach a file.`,
    );
  }

  const now = new Date();
  await db
    .update(interests)
    .set({ [column]: 'signed', updatedAt: now })
    .where(and(eq(interests.id, input.interestId), eq(interests.portId, input.portId)));

  void createAuditLog({
    userId: meta.userId,
    portId: input.portId,
    action: 'update',
    entityType: 'interest',
    entityId: input.interestId,
    oldValue: { [column]: previousStatus },
    newValue: { [column]: 'signed', reason: input.reason ?? null },
    metadata: { type: 'externally_signed', docType: input.docType, withoutFile: true },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });

  emitToRoom(`port:${input.portId}`, 'interest:updated', {
    interestId: input.interestId,
    changedFields: [column, 'updatedAt'],
  });

  const trigger = BERTH_RULE_TRIGGERS[input.docType];
  if (trigger) {
    void evaluateRule(trigger, input.interestId, input.portId, meta);
  }

  return { docType: input.docType, previousStatus };
}
