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

import { type RouteHandler } from '@/lib/api/helpers';
import { parseBody } from '@/lib/api/route-helpers';
import { errorResponse, NotFoundError, ValidationError } from '@/lib/errors';
import { db } from '@/lib/db';
import { interests, interestBerths } from '@/lib/db/schema/interests';
import { berths } from '@/lib/db/schema/berths';
import { removeInterestBerth, upsertInterestBerth } from '@/lib/services/interest-berths.service';
import { createAuditLog } from '@/lib/audit';
import { emitToRoom } from '@/lib/socket/server';

// ─── Schemas ────────────────────────────────────────────────────────────────

/**
 * Partial update of a junction row's role flags + EOI bypass fields. Every
 * field is optional; passing only the ones the rep wants to change.
 *
 * `eoiBypassReason` is a tri-state:
 *   - omitted     → no change
 *   - non-empty   → record bypass (server stamps `eoiBypassedAt = now()` and
 *                   `eoiBypassedBy = caller`)
 *   - null        → clear bypass (also clears `eoiBypassedBy` / `eoiBypassedAt`)
 */
const patchBerthSchema = z
  .object({
    isPrimary: z.boolean().optional(),
    isSpecificInterest: z.boolean().optional(),
    isInEoiBundle: z.boolean().optional(),
    eoiBypassReason: z.string().max(2000).nullable().optional(),
  })
  .refine((v) => Object.values(v).some((x) => x !== undefined), {
    message: 'At least one field must be provided.',
  });

// ─── Helpers ────────────────────────────────────────────────────────────────

async function loadScopedRow(interestId: string, berthId: string, portId: string) {
  // Verify interest port-scope first so unrelated 404s look identical to a
  // truly-missing row (enumeration prevention - plan §14.10).
  const interest = await db.query.interests.findFirst({
    where: eq(interests.id, interestId),
  });
  if (!interest || interest.portId !== portId) {
    throw new NotFoundError('Interest');
  }
  const link = await db.query.interestBerths.findFirst({
    where: and(eq(interestBerths.interestId, interestId), eq(interestBerths.berthId, berthId)),
  });
  if (!link) {
    throw new NotFoundError('Berth link');
  }
  // Also confirm the berth itself is in-port; defensive against a junction row
  // pointing at a foreign berth (shouldn't happen, but cheap to check).
  const berth = await db.query.berths.findFirst({
    where: and(eq(berths.id, berthId), eq(berths.portId, portId)),
  });
  if (!berth) {
    throw new NotFoundError('Berth');
  }
  return { interest, link, berth };
}

// ─── PATCH /api/v1/interests/[id]/berths/[berthId] ──────────────────────────
export const patchHandler: RouteHandler = async (req, ctx, params) => {
  try {
    const interestId = params.id!;
    const berthId = params.berthId!;
    const body = await parseBody(req, patchBerthSchema);

    const { interest } = await loadScopedRow(interestId, berthId, ctx.portId);

    // Plan §5.5: the bypass control is only available once the interest's
    // primary EOI is signed. Defend the API too - never trust the UI to
    // gate this.
    if (body.eoiBypassReason !== undefined && interest.eoiStatus !== 'signed') {
      throw new ValidationError('EOI bypass requires a signed primary EOI on the interest');
    }

    const updated = await upsertInterestBerth(interestId, berthId, {
      isPrimary: body.isPrimary,
      isSpecificInterest: body.isSpecificInterest,
      isInEoiBundle: body.isInEoiBundle,
      eoiBypassReason: body.eoiBypassReason,
      eoiBypassedBy: body.eoiBypassReason ? ctx.userId : null,
    });

    void createAuditLog({
      userId: ctx.userId,
      portId: ctx.portId,
      action: 'update',
      entityType: 'interest',
      entityId: interestId,
      newValue: { berthId, ...body },
      metadata: { type: 'berth_link_updated' },
      ipAddress: ctx.ipAddress,
      userAgent: ctx.userAgent,
    });

    emitToRoom(`port:${ctx.portId}`, 'interest:berthLinkUpdated', {
      interestId,
      berthId,
    });
    void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) =>
      dispatchWebhookEvent(ctx.portId, 'interest:berthLinkUpdated', {
        interestId,
        berthId,
      }),
    );

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

// ─── DELETE /api/v1/interests/[id]/berths/[berthId] ─────────────────────────
export const deleteHandler: RouteHandler = async (_req, ctx, params) => {
  try {
    const interestId = params.id!;
    const berthId = params.berthId!;

    await loadScopedRow(interestId, berthId, ctx.portId);

    await removeInterestBerth(interestId, berthId, ctx.portId, {
      userId: ctx.userId,
      portId: ctx.portId,
      ipAddress: ctx.ipAddress,
      userAgent: ctx.userAgent,
    });

    void createAuditLog({
      userId: ctx.userId,
      portId: ctx.portId,
      action: 'update',
      entityType: 'interest',
      entityId: interestId,
      oldValue: { berthId },
      metadata: { type: 'berth_removed_from_interest' },
      ipAddress: ctx.ipAddress,
      userAgent: ctx.userAgent,
    });

    emitToRoom(`port:${ctx.portId}`, 'interest:berthUnlinked', {
      interestId,
      berthId,
    });
    void import('@/lib/services/webhook-dispatch').then(({ dispatchWebhookEvent }) =>
      dispatchWebhookEvent(ctx.portId, 'interest:berthUnlinked', {
        interestId,
        berthId,
      }),
    );

    return new NextResponse(null, { status: 204 });
  } catch (error) {
    return errorResponse(error);
  }
};
