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

import { withAuth, withPermission } from '@/lib/api/helpers';
import { CLIENT_ARCHIVE_ACTION } from '@/lib/auth/permissions';
import { parseBody } from '@/lib/api/route-helpers';
import {
  archiveClientWithDecisions,
  type ArchiveDecisions,
} from '@/lib/services/client-archive.service';
import { getClientArchiveDossier } from '@/lib/services/client-archive-dossier.service';
import { notifyNextInLine } from '@/lib/services/next-in-line-notify.service';
import { getQueue } from '@/lib/queue';
import { logger } from '@/lib/logger';
import { errorResponse, NotFoundError } from '@/lib/errors';

const decisionsSchema = z.object({
  reason: z.string().max(2000).default(''),
  acknowledgedSignedDocuments: z.boolean().default(false),
  berthDecisions: z
    .array(
      z.object({
        berthId: z.string().min(1),
        interestId: z.string().min(1),
        action: z.enum(['release', 'retain']),
      }),
    )
    .default([]),
  yachtDecisions: z
    .array(
      z.object({
        yachtId: z.string().min(1),
        action: z.enum(['transfer', 'mark_sold_away', 'retain']),
        newOwnerType: z.enum(['client', 'company']).optional(),
        newOwnerId: z.string().min(1).optional(),
      }),
    )
    .default([]),
  tenancyDecisions: z
    .array(
      z.object({
        tenancyId: z.string().min(1),
        action: z.enum(['cancel', 'transfer']),
        transferToClientId: z.string().min(1).optional(),
      }),
    )
    .default([]),
  invoiceDecisions: z
    .array(
      z.object({
        invoiceId: z.string().min(1),
        action: z.enum(['void', 'write_off', 'leave']),
      }),
    )
    .default([]),
  documentDecisions: z
    .array(
      z.object({
        documentId: z.string().min(1),
        action: z.enum(['void_documenso', 'leave']),
      }),
    )
    .default([]),
});

export const POST = withAuth(
  withPermission('clients', CLIENT_ARCHIVE_ACTION, async (req, ctx, params) => {
    try {
      const id = params.id;
      if (!id) throw new NotFoundError('client');
      const body = await parseBody(req, decisionsSchema);

      // Re-fetch the dossier inside the request so the service has the
      // current state of the world (the UI's dossier might be stale).
      const dossier = await getClientArchiveDossier(id, ctx.portId);
      const result = await archiveClientWithDecisions({
        dossier,
        decisions: body satisfies ArchiveDecisions,
        meta: {
          userId: ctx.userId,
          portId: ctx.portId,
          ipAddress: ctx.ipAddress,
          userAgent: ctx.userAgent,
        },
      });

      // ─── Post-commit side-effects ────────────────────────────────────
      // 1. Documenso envelope voids → queued for retry on the documents
      //    queue. Permanently-failed jobs land in BullMQ's failed jobs
      //    list (the DLQ in admin/monitoring).
      if (result.externalCleanups.length > 0) {
        const queue = getQueue('documents');
        for (const c of result.externalCleanups) {
          if (c.kind === 'documenso_void') {
            await queue
              .add('documenso-void', {
                documentId: c.documentId,
                documensoId: c.documensoId,
                portId: ctx.portId,
              })
              .catch((err) =>
                logger.error({ err, documentId: c.documentId }, 'Failed to enqueue Documenso void'),
              );
          }
        }
      }

      // 2. Next-in-line notifications → fire-and-forget per released
      //    berth so the sales team knows who else expressed interest.
      //    No automatic stage transitions on the next interests; sales
      //    rep decides what to do.
      for (const released of result.releasedBerths) {
        if (released.nextInLineInterestIds.length === 0) continue;
        const otherInterests =
          dossier.berths
            .find((b) => b.berthId === released.berthId)
            ?.otherInterests.map((o) => ({
              interestId: o.interestId,
              clientName: o.clientName,
              pipelineStage: o.pipelineStage,
            })) ?? [];
        void notifyNextInLine({
          portId: ctx.portId,
          berthId: released.berthId,
          mooringNumber: released.mooringNumber,
          archivedClientName: dossier.client.fullName,
          nextInLineInterests: otherInterests,
        }).catch((err) =>
          logger.error(
            { err, berthId: released.berthId },
            'Failed to fire next-in-line notification',
          ),
        );
      }

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