// Shared core: the dispatch engine's Stage 2 — DESTINATION resolution loop.
// Recipient-independent. One PENDING DestinationDeliveryLog per active
// ChannelRoutingBinding whose (targetType, targetId) matches the event's
// (sourceType, sourceId) and whose channel is an enabled DESTINATION-kind
// channel. The rendered subject/body are captured on the log so the delivery
// worker (internal/deliverDestination) can post from the row alone.

import { type ReadonlyDB } from "@tailor-platform/erp-kit/core";
import type { DB, Transaction } from "../generated/kysely-tailordb";
import {
  DEFAULT_LOCALE,
  loadChannelByChannelId,
  loadTemplateWithFallback,
} from "../lib/dispatchQueries";
import { interpolate } from "../lib/templateRendering";

export interface PlannedDestination {
  bindingId: string;
  channelId: string;
  externalChannelRef: string;
  logId: string;
  status: "PENDING" | "FAILED" | "SENT";
  reason?: string;
}

interface ChannelRoutingBindingRow {
  id: string;
  targetType: string;
  targetId: string;
  channelId: string;
  externalChannelRef: string;
  displayLabel: string | null;
  isActive: boolean;
}

async function loadActiveBindings(
  db: ReadonlyDB<DB>,
  targetType: string,
  targetId: string,
): Promise<ChannelRoutingBindingRow[]> {
  return (await db
    .selectFrom("ChannelRoutingBinding")
    .selectAll()
    .where("targetType", "=", targetType)
    .where("targetId", "=", targetId)
    .where("isActive", "=", true)
    .execute()) as ChannelRoutingBindingRow[];
}

// Belt-and-suspenders idempotency, symmetric with the PERSONAL path's
// loadExistingNotification: a DestinationDeliveryLog is keyed by the event's
// logical identity `(idempotencyKey)` and the post target
// `(channelId, externalChannelRef)`, so re-planning the same logical event
// (e.g. a duplicate NotificationEvent row that slipped past the ingress dedup)
// reuses the existing log instead of posting to the shared channel twice. The
// idempotencyKey carries the payloadHash, so two *distinct* events for the same
// (eventType, sourceType, sourceId) still get separate posts.
async function loadExistingLog(
  db: Transaction,
  idempotencyKey: string,
  channelId: string,
  externalChannelRef: string,
): Promise<{ id: string; status: "PENDING" | "SENT" | "FAILED" } | undefined> {
  return (await db
    .selectFrom("DestinationDeliveryLog")
    .select(["id", "status"])
    .where("idempotencyKey", "=", idempotencyKey)
    .where("channelId", "=", channelId)
    .where("externalChannelRef", "=", externalChannelRef)
    .executeTakeFirst()) as { id: string; status: "PENDING" | "SENT" | "FAILED" } | undefined;
}

async function insertLog(
  db: Transaction,
  row: {
    idempotencyKey: string;
    eventType: string;
    sourceType: string;
    sourceId: string;
    targetType: string;
    targetId: string;
    channelId: string;
    externalChannelRef: string;
    subject: string;
    body: string;
    status: "PENDING" | "FAILED";
    failureReason: string | null;
  },
  now: Date,
): Promise<string> {
  const id = crypto.randomUUID();
  await db
    .insertInto("DestinationDeliveryLog")
    .values({
      id,
      ...row,
      providerMessageId: null,
      providerResponse: null,
      retryCount: 0,
      attemptedAt: now,
      completedAt: row.status === "FAILED" ? now : null,
      createdAt: now,
      updatedAt: now,
    })
    .execute();
  return id;
}

export interface PlanDestinationsArgs {
  eventType: string;
  sourceType: string;
  sourceId: string;
  /** Event logical-identity key (eventType:sourceType:sourceId:payloadHash) used to dedup logs across re-plans. */
  idempotencyKey: string;
  locale?: string;
  payloadVars: Record<string, unknown>;
}

export async function planDestinations(
  db: Transaction,
  args: PlanDestinationsArgs,
  now: Date,
): Promise<PlannedDestination[]> {
  const out: PlannedDestination[] = [];
  const bindings = await loadActiveBindings(db, args.sourceType, args.sourceId);

  for (const b of bindings) {
    const channel = await loadChannelByChannelId(db, b.channelId);
    if (!channel || !channel.enabled || channel.kind !== "DESTINATION") continue;

    // Reuse an already-planned log for this (event identity, post target) rather
    // than inserting a duplicate that would post to the shared channel twice.
    const existing = await loadExistingLog(
      db,
      args.idempotencyKey,
      b.channelId,
      b.externalChannelRef,
    );
    if (existing) {
      out.push({
        bindingId: b.id,
        channelId: b.channelId,
        externalChannelRef: b.externalChannelRef,
        logId: existing.id,
        status: existing.status,
        reason: "DEDUPED",
      });
      continue;
    }

    const locale = args.locale ?? DEFAULT_LOCALE;
    const template = await loadTemplateWithFallback(db, args.eventType, channel.id, locale);
    if (!template) {
      const logId = await insertLog(
        db,
        {
          idempotencyKey: args.idempotencyKey,
          eventType: args.eventType,
          sourceType: args.sourceType,
          sourceId: args.sourceId,
          targetType: b.targetType,
          targetId: b.targetId,
          channelId: b.channelId,
          externalChannelRef: b.externalChannelRef,
          subject: "",
          body: "",
          status: "FAILED",
          failureReason: "template_not_found",
        },
        now,
      );
      out.push({
        bindingId: b.id,
        channelId: b.channelId,
        externalChannelRef: b.externalChannelRef,
        logId,
        status: "FAILED",
        reason: "TEMPLATE_NOT_FOUND",
      });
      continue;
    }

    const subject = interpolate(template.subject, args.payloadVars, false);
    const body = interpolate(template.body, args.payloadVars, false);
    const logId = await insertLog(
      db,
      {
        idempotencyKey: args.idempotencyKey,
        eventType: args.eventType,
        sourceType: args.sourceType,
        sourceId: args.sourceId,
        targetType: b.targetType,
        targetId: b.targetId,
        channelId: b.channelId,
        externalChannelRef: b.externalChannelRef,
        subject,
        body,
        status: "PENDING",
        failureReason: null,
      },
      now,
    );
    out.push({
      bindingId: b.id,
      channelId: b.channelId,
      externalChannelRef: b.externalChannelRef,
      logId,
      status: "PENDING",
    });
  }

  return out;
}
