import type { RedisClient, RedisWithClusterOptions } from "~/redis.server";
import { createRedisClient } from "~/redis.server";
import { EventEmitter } from "node:events";
import { env } from "~/env.server";
import { singleton } from "~/utils/singleton";
import { Gauge } from "prom-client";
import { metricsRegister } from "~/metrics.server";

export type TracePubSubOptions = {
  redis: RedisWithClusterOptions;
};

export class TracePubSub {
  private _publisher: RedisClient;
  private _subscriberCount = 0;

  constructor(private _options: TracePubSubOptions) {
    this._publisher = createRedisClient("trigger:eventRepoPublisher", this._options.redis);
  }

  get subscriberCount() {
    return this._subscriberCount;
  }

  async publish(traceIds: string[]) {
    if (traceIds.length === 0) return;
    const uniqueTraces = new Set(traceIds.map((e) => `events:${e}`));

    await Promise.allSettled(
      Array.from(uniqueTraces).map((traceId) =>
        this._publisher.publish(traceId, new Date().toISOString())
      )
    );
  }

  async subscribeToTrace(traceId: string) {
    const redis = createRedisClient("trigger:eventRepoSubscriber", this._options.redis);

    const channel = `events:${traceId}`;

    // Subscribe to the channel.
    await redis.subscribe(channel);

    // Increment the subscriber count.
    this._subscriberCount++;

    const eventEmitter = new EventEmitter();

    // Define the message handler - store reference so we can remove it later.
    const messageHandler = (_: string, message: string) => {
      eventEmitter.emit("message", message);
    };
    redis.on("message", messageHandler);

    // Return a function that can be used to unsubscribe.
    const unsubscribe = async () => {
      // Remove the message listener before closing the connection
      redis.off("message", messageHandler);
      await redis.unsubscribe(channel);
      await redis.quit();
      this._subscriberCount--;
    };

    return {
      unsubscribe,
      eventEmitter,
    };
  }
}

export const tracePubSub = singleton("tracePubSub", initializeTracePubSub);

function initializeTracePubSub() {
  const pubSub = new TracePubSub({
    redis: {
      port: env.PUBSUB_REDIS_PORT,
      host: env.PUBSUB_REDIS_HOST,
      username: env.PUBSUB_REDIS_USERNAME,
      password: env.PUBSUB_REDIS_PASSWORD,
      tlsDisabled: env.PUBSUB_REDIS_TLS_DISABLED === "true",
      clusterMode: env.PUBSUB_REDIS_CLUSTER_MODE_ENABLED === "1",
    },
  });

  new Gauge({
    name: "trace_pub_sub_subscribers",
    help: "Number of trace pub sub subscribers",
    collect() {
      this.set(pubSub.subscriberCount);
    },
    registers: [metricsRegister],
  });

  return pubSub;
}
