/**
 * "Next in line" notification fan-out.
 *
 * After a smart-archive releases a berth back to available, the sales
 * team should be told who else expressed interest in that berth so they
 * can follow up. This is informational only - no automatic stage
 * transitions on the next interests.
 *
 * Recipients = port users whose role grants `interests.change_stage`
 * (the canonical "this person handles the pipeline" permission).
 */

import { asc, eq } from 'drizzle-orm';

import { db } from '@/lib/db';
import { userPortRoles, roles, type RolePermissions } from '@/lib/db/schema/users';
import { berthWaitingList } from '@/lib/db/schema/berths';
import { clients } from '@/lib/db/schema/clients';
import { logger } from '@/lib/logger';
import { createNotification } from '@/lib/services/notifications.service';
import { STAGE_LABELS, type PipelineStage } from '@/lib/constants';

export interface BerthReleaseNotificationInput {
  portId: string;
  berthId: string;
  mooringNumber: string;
  archivedClientName: string;
  /** ids of the next-in-line interests on this berth (with the metadata
   *  needed for the notification body - comes from the dossier). */
  nextInLineInterests: Array<{
    interestId: string;
    clientName: string | null;
    pipelineStage: string;
  }>;
}

export async function notifyNextInLine(input: BerthReleaseNotificationInput): Promise<void> {
  // 1. Resolve recipients: every port user whose role permits interests.change_stage.
  const portRoleRows = await db
    .select({
      userId: userPortRoles.userId,
      permissions: roles.permissions,
    })
    .from(userPortRoles)
    .innerJoin(roles, eq(userPortRoles.roleId, roles.id))
    .where(eq(userPortRoles.portId, input.portId));

  const salesUserIds = new Set<string>();
  for (const r of portRoleRows) {
    const perms = r.permissions as RolePermissions | null;
    if (perms?.interests?.change_stage) salesUserIds.add(r.userId);
  }

  if (salesUserIds.size === 0) {
    logger.debug(
      { portId: input.portId, berthId: input.berthId },
      'No sales recipients for next-in-line notification',
    );
    return;
  }

  // 2. Build a single description listing the next interests so the
  //    rep can act on it without opening the berth detail page first.
  const previewLines = input.nextInLineInterests.slice(0, 5).map((i) => {
    const stageLabel =
      STAGE_LABELS[i.pipelineStage as PipelineStage] ?? i.pipelineStage.replace(/_/g, ' ');
    return `${i.clientName ?? '(unknown)'} - ${stageLabel}`;
  });
  const more =
    input.nextInLineInterests.length > 5
      ? `\n…and ${input.nextInLineInterests.length - 5} more`
      : '';
  const description = input.nextInLineInterests.length
    ? `${previewLines.join('\n')}${more}`
    : 'No prior interests recorded - this berth is fully available again.';

  // 3. Fire-and-forget per recipient. dedupeKey collapses duplicate
  //    fires within the cooldown window if multiple events queue up.
  for (const userId of salesUserIds) {
    void createNotification({
      portId: input.portId,
      userId,
      type: 'berth_released',
      title: `Berth ${input.mooringNumber} released - ${input.archivedClientName} archived`,
      description,
      link: `/berths/${input.berthId}`,
      entityType: 'berth',
      entityId: input.berthId,
      dedupeKey: `berth-released:${input.berthId}`,
    });
  }
}

/**
 * Waiting-list next-in-line. When a berth transitions to `available`, surface
 * the #1 client on that berth's waiting list to the staff who manage the
 * queue so they can reach out / open a deal. Informational only — no
 * automatic linking or stage changes.
 *
 * Recipients = port users whose role grants `berths.manage_waiting_list`.
 * No-ops when the waiting list is empty or no one holds the permission.
 */
export async function notifyWaitlistNextInLine(input: {
  portId: string;
  berthId: string;
  mooringNumber: string;
}): Promise<void> {
  // #1 on the queue (lowest position). Left-join the client for the name.
  const [next] = await db
    .select({
      clientName: clients.fullName,
      priority: berthWaitingList.priority,
    })
    .from(berthWaitingList)
    .leftJoin(clients, eq(clients.id, berthWaitingList.clientId))
    .where(eq(berthWaitingList.berthId, input.berthId))
    .orderBy(asc(berthWaitingList.position))
    .limit(1);

  if (!next) return; // empty waiting list - nothing to surface

  const recipientRows = await db
    .select({ userId: userPortRoles.userId, permissions: roles.permissions })
    .from(userPortRoles)
    .innerJoin(roles, eq(userPortRoles.roleId, roles.id))
    .where(eq(userPortRoles.portId, input.portId));

  const recipientIds = new Set<string>();
  for (const r of recipientRows) {
    const perms = r.permissions as RolePermissions | null;
    if (perms?.berths?.manage_waiting_list) recipientIds.add(r.userId);
  }
  if (recipientIds.size === 0) {
    logger.debug(
      { portId: input.portId, berthId: input.berthId },
      'No waiting-list managers to notify of berth availability',
    );
    return;
  }

  const clientLabel = next.clientName ?? '(unknown client)';
  const priorityNote = next.priority === 'high' ? ' (high priority)' : '';

  for (const userId of recipientIds) {
    void createNotification({
      portId: input.portId,
      userId,
      type: 'berth_waiting_list_update',
      title: `Berth ${input.mooringNumber} is now available`,
      description: `${clientLabel} is next in line${priorityNote}. Open the berth to action the waiting list.`,
      link: `/berths/${input.berthId}`,
      entityType: 'berth',
      entityId: input.berthId,
      dedupeKey: `berth-waitlist-available:${input.berthId}`,
    });
  }
}
