import { getMeter } from "@internal/tracing";
import { env } from "~/env.server";
import { singleton } from "~/utils/singleton";
import { logger } from "../logger.server";
import { RunChangeNotifier, type ChangeRecordInput } from "./runChangeNotifier.server";

/**
 * Process-singleton wiring for the RunChangeNotifier plus the gated convenience functions write sites
 * delegate to. The notifier is constructed lazily, so `REALTIME_BACKEND_NATIVE_ENABLED=0` (default) opens no Redis connections.
 */
const nativeBackendEnabled = env.REALTIME_BACKEND_NATIVE_ENABLED === "1";

function initializeRunChangeNotifier(): RunChangeNotifier {
  const clusterMode = env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_CLUSTER_MODE_ENABLED === "1";
  // Sharded pub/sub only works against a cluster; classic pub/sub there would
  // broadcast every message to every node, so this is what actually shards load.
  const shardedPubSub =
    clusterMode && env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_SHARDED_ENABLED === "1";

  const meter = getMeter("realtime-notifier");

  const publishes = meter.createCounter("realtime_notifier.publishes", {
    description:
      "Change-record publishes by outcome. Failures are the leading indicator that feeds are degrading to their backstops (pub/sub Redis trouble).",
  });

  const received = meter.createCounter("realtime_notifier.messages_received", {
    description: "Raw channel messages received by this instance's subscriber, pre-coalesce.",
  });

  const delivered = meter.createCounter("realtime_notifier.batches_delivered", {
    description:
      "Coalesced batches delivered to listeners. received/batches = the coalesce ratio (how hard a busy env is being collapsed).",
  });

  const notifier = new RunChangeNotifier({
    redis: {
      host: env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_HOST,
      port: env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_PORT,
      username: env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_USERNAME,
      password: env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_PASSWORD,
      tlsDisabled: env.REALTIME_BACKEND_NATIVE_PUBSUB_REDIS_TLS_DISABLED === "true",
      clusterMode,
      // One subscriber connection per shard so SSUBSCRIBE routes to the slot owner.
      ...(shardedPubSub ? { clusterOptions: { shardedSubscribers: true } } : {}),
    },
    envWakeCoalesceWindowMs: env.REALTIME_BACKEND_NATIVE_ENV_WAKE_COALESCE_WINDOW_MS,
    shardedPubSub,
    onPublishResult: (ok) => publishes.add(1, { result: ok ? "ok" : "error" }),
    onMessageReceived: () => received.add(1),
    onBatchDelivered: () => delivered.add(1),
  });

  meter
    .createObservableGauge("realtime_notifier.active_subscriptions", {
      description: "Distinct env channels currently subscribed for realtime change notifications.",
    })
    .addCallback((result) => result.observe(notifier.activeSubscriptionCount));

  return notifier;
}

/** Lazily construct (and memoize) the notifier singleton. */
export function getRunChangeNotifier(): RunChangeNotifier {
  return singleton("runChangeNotifier", initializeRunChangeNotifier);
}

/** Whether the notifier subsystem is enabled for this process. */
export function isRunChangeNotifierEnabled(): boolean {
  return nativeBackendEnabled;
}

/** Fire-and-forget publish of a run-changed record. No-op (and no notifier construction)
 * when disabled, so publish sites can call it unconditionally. */
export function publishChangeRecord(input: ChangeRecordInput): void {
  if (!nativeBackendEnabled) {
    return;
  }
  // Publish runs on the run-engine event bus / metadata flush loop; lazy init + encoding happen
  // before the notifier's own try/catch, so guard the whole call — it must never throw at its caller.
  try {
    getRunChangeNotifier().publish(input);
  } catch (error) {
    logger.error("[runChangeNotifier] publishChangeRecord threw; dropping notification", { error });
  }
}

export function publishManyChangeRecords(inputs: ChangeRecordInput[]): void {
  if (!nativeBackendEnabled) {
    return;
  }
  try {
    getRunChangeNotifier().publishMany(inputs);
  } catch (error) {
    logger.error("[runChangeNotifier] publishManyChangeRecords threw; dropping notifications", {
      error,
    });
  }
}
