import { logger, type RuntimeEnvironmentType } from "@trigger.dev/core/v3";
import { type RunEngineVersion } from "@trigger.dev/database";
import { ServiceValidationError } from "~/v3/services/baseService.server";
import { BasePresenter } from "./basePresenter.server";
import { waitpointStatusToApiStatus } from "./WaitpointListPresenter.server";
import { generateHttpCallbackUrl } from "~/services/httpCallback.server";
import type { PrismaClientOrTransaction, PrismaReplicaClient } from "~/db.server";
import { readThroughRun } from "~/v3/runOpsMigration/readThrough.server";

// When omitted, clients default to the inherited _replica handle => passthrough reads the
// replica exactly as today. isPastRetention is injectable for tests. Typed PrismaReplicaClient
// to match readThroughRun's readNew/readLegacy + deps.
type ApiWaitpointPresenterReadThroughDeps = {
  newClient?: PrismaReplicaClient;
  legacyReplica?: PrismaReplicaClient;
  splitEnabled?: boolean;
  isPastRetention?: (id: string) => boolean;
};

export class ApiWaitpointPresenter extends BasePresenter {
  constructor(
    prismaClient?: PrismaClientOrTransaction,
    replicaClient?: PrismaClientOrTransaction,
    private readonly readThroughDeps?: ApiWaitpointPresenterReadThroughDeps
  ) {
    super(prismaClient, replicaClient);
  }

  public async call(
    environment: {
      id: string;
      type: RuntimeEnvironmentType;
      project: {
        id: string;
        engine: RunEngineVersion;
      };
      apiKey: string;
    },
    waitpointId: string
  ) {
    return this.trace("call", async (span) => {
      // Public waitpoint retrieve. Split on: new run-ops client first, then the LEGACY
      // RUN-OPS READ REPLICA ONLY on a new-probe miss — never the legacy primary.
      // Split off (single-DB / self-host): one plain waitpoint.findFirst against the replica
      // (passthrough). The waitpointId is the residency-classifiable run-ops id (the route
      // pre-decodes the friendlyId via WaitpointId.toId).
      const hydrate = (client: PrismaReplicaClient) =>
        client.waitpoint.findFirst({
          where: {
            id: waitpointId,
            environmentId: environment.id,
          },
          select: {
            id: true,
            friendlyId: true,
            type: true,
            status: true,
            idempotencyKey: true,
            userProvidedIdempotencyKey: true,
            idempotencyKeyExpiresAt: true,
            inactiveIdempotencyKey: true,
            output: true,
            outputType: true,
            outputIsError: true,
            completedAfter: true,
            completedAt: true,
            createdAt: true,
            tags: true,
          },
        });

      const result = await readThroughRun({
        runId: waitpointId,
        environmentId: environment.id,
        readNew: (client) => hydrate(client),
        readLegacy: (replica) => hydrate(replica),
        deps: {
          splitEnabled: this.readThroughDeps?.splitEnabled,
          // Default both clients to the inherited _replica handle (declared
          // PrismaClientOrTransaction but $replica at runtime) so passthrough reads the replica
          // as today; split mode injects a distinct newClient.
          newClient: this.readThroughDeps?.newClient ?? (this._replica as PrismaReplicaClient),
          legacyReplica:
            this.readThroughDeps?.legacyReplica ?? (this._replica as PrismaReplicaClient),
          isPastRetention: this.readThroughDeps?.isPastRetention,
        },
      });

      const waitpoint =
        result.source === "new" || result.source === "legacy-replica" ? result.value : null;

      if (!waitpoint) {
        logger.error(`WaitpointPresenter: Waitpoint not found`, {
          id: waitpointId,
        });
        throw new ServiceValidationError("Waitpoint not found");
      }

      let _isTimeout = false;
      if (waitpoint.outputIsError && waitpoint.output) {
        _isTimeout = true;
      }

      return {
        id: waitpoint.friendlyId,
        type: waitpoint.type,
        url: generateHttpCallbackUrl(waitpoint.id, environment.apiKey),
        status: waitpointStatusToApiStatus(waitpoint.status, waitpoint.outputIsError),
        idempotencyKey: waitpoint.idempotencyKey,
        userProvidedIdempotencyKey: waitpoint.userProvidedIdempotencyKey,
        idempotencyKeyExpiresAt: waitpoint.idempotencyKeyExpiresAt ?? undefined,
        inactiveIdempotencyKey: waitpoint.inactiveIdempotencyKey ?? undefined,
        output: waitpoint.output ?? undefined,
        outputType: waitpoint.outputType,
        outputIsError: waitpoint.outputIsError,
        timeoutAt: waitpoint.completedAfter ?? undefined,
        completedAfter: waitpoint.completedAfter ?? undefined,
        completedAt: waitpoint.completedAt ?? undefined,
        createdAt: waitpoint.createdAt,
        tags: waitpoint.tags,
      };
    });
  }
}
