import type { PrismaClientOrTransaction } from "~/db.server";
import { logger } from "~/services/logger.server";
import { commonWorker } from "../commonWorker.server";
import { BaseService } from "./baseService.server";
import { FinalizeTaskRunService } from "./finalizeTaskRun.server";
import { tryCatch } from "@trigger.dev/core/utils";
import { getEventRepositoryForStore } from "../eventRepository/index.server";
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
import { isV3Disabled } from "../engineDeprecation.server";

export class ExpireEnqueuedRunService extends BaseService {
  public static async ack(runId: string, tx?: PrismaClientOrTransaction) {
    // We don't "dequeue" from the workerQueue here because it would be redundant and if this service
    // is called for a run that has already started, nothing happens
    await commonWorker.ack(`v3.expireRun:${runId}`);
  }

  public static async enqueue(runId: string, runAt?: Date) {
    return await commonWorker.enqueue({
      job: "v3.expireRun",
      payload: { runId },
      availableAt: runAt,
      id: `v3.expireRun:${runId}`,
    });
  }

  public async call(runId: string) {
    const run = await this.runStore.findRun(
      {
        id: runId,
      },
      {
        select: {
          id: true,
          status: true,
          engine: true,
          lockedAt: true,
          ttl: true,
          taskEventStore: true,
          runtimeEnvironmentId: true,
          friendlyId: true,
          traceId: true,
          spanId: true,
          parentSpanId: true,
          createdAt: true,
          completedAt: true,
          taskIdentifier: true,
          projectId: true,
          organizationId: true,
          isTest: true,
        },
      },
      this._prisma
    );

    if (!run) {
      logger.debug("Could not find enqueued run to expire", {
        runId,
      });

      return;
    }

    // v3 (engine V1) shutdown: skip expiring abandoned V1 runs. v4 is unaffected.
    if (isV3Disabled() && run.engine === "V1") {
      logger.debug("[ExpireEnqueuedRunService] Skipping expiry for shut-down v3 run", { runId });
      return;
    }

    const env = await controlPlaneResolver.resolveEnv(run.runtimeEnvironmentId);

    if (!env) {
      logger.debug("ExpireEnqueuedRunService: environment not found", { runId });
      return;
    }

    if (run.status !== "PENDING") {
      logger.debug("Run cannot be expired because it's not in PENDING status", {
        run,
      });

      return;
    }

    if (run.lockedAt) {
      logger.debug("Run cannot be expired because it's locked", {
        run,
      });

      return;
    }

    logger.debug("Expiring enqueued run", {
      run,
    });

    const finalizeService = new FinalizeTaskRunService();
    await finalizeService.call({
      id: run.id,
      status: "EXPIRED",
      expiredAt: new Date(),
      completedAt: new Date(),
      attemptStatus: "FAILED",
      error: {
        type: "STRING_ERROR",
        raw: `Run expired because the TTL (${run.ttl}) was reached`,
      },
    });

    const eventRepository = await getEventRepositoryForStore(
      run.taskEventStore,
      env.organizationId
    );

    if (run.ttl) {
      const [completeExpiredRunEventError] = await tryCatch(
        eventRepository.completeExpiredRunEvent({
          run,
          endTime: new Date(),
          ttl: run.ttl,
        })
      );

      if (completeExpiredRunEventError) {
        logger.error("[ExpireEnqueuedRunService] Failed to complete expired run event", {
          error: completeExpiredRunEventError,
          runId: run.id,
        });
      }
    }
  }
}
