import { getMeter } from "@internal/tracing";
import { $replica } from "~/db.server";
import { runStore } from "~/v3/runStore.server";
import { env } from "~/env.server";
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server";
import { singleton } from "~/utils/singleton";
import { realtimeClient } from "../realtimeClientGlobal.server";
import { ClickHouseRunListResolver } from "./clickHouseRunListResolver.server";
import { RunHydrator } from "./runReader.server";
import { RealtimeShadowComparator } from "./shadowCompare.server";
import { ShadowRealtimeClient } from "./shadowRealtimeClient.server";

/**
 * Process-singleton wiring for the shadow-compare client. Only constructed
 * when an org's `realtimeBackend` flag is set to "shadow".
 */
function initializeShadowRealtimeClient(): ShadowRealtimeClient {
  const compares = getMeter("realtime-shadow").createCounter("realtime_shadow.compares", {
    description:
      "Dual-run shadow-compare outcomes (Electric vs native). kind=serialization|membership, result=match|diverge|skew.",
  });

  const comparator = new RealtimeShadowComparator({
    runReader: new RunHydrator({ replica: $replica, runStore }),
    runListResolver: new ClickHouseRunListResolver({
      getClickhouse: (organizationId) =>
        clickhouseFactory.getClickhouseForOrganization(organizationId, "realtime"),
      prisma: $replica,
    }),
  });

  return new ShadowRealtimeClient({
    electric: realtimeClient,
    comparator,
    maximumCreatedAtFilterAgeMs: env.REALTIME_MAXIMUM_CREATED_AT_FILTER_AGE_IN_MS,
    maxListResults: env.REALTIME_BACKEND_NATIVE_MAX_LIST_RESULTS,
    onOutcome: (outcome) => {
      const { feed } = outcome;
      if (outcome.serializationMatched) {
        compares.add(outcome.serializationMatched, {
          feed,
          kind: "serialization",
          result: "match",
        });
      }
      if (outcome.serializationDiverged) {
        compares.add(outcome.serializationDiverged, {
          feed,
          kind: "serialization",
          result: "diverge",
        });
      }
      if (outcome.serializationSkew) {
        compares.add(outcome.serializationSkew, { feed, kind: "serialization", result: "skew" });
      }
      if (outcome.membershipMatch !== undefined) {
        compares.add(1, {
          feed,
          kind: "membership",
          result: outcome.membershipMatch ? "match" : "diverge",
        });
      }
    },
  });
}

export function getShadowRealtimeClient(): ShadowRealtimeClient {
  return singleton("shadowRealtimeClient", initializeShadowRealtimeClient);
}
