import { logger } from "~/services/logger.server";
import { BaseService } from "./baseService.server";
import { commonWorker } from "../commonWorker.server";
import { PerformDeploymentAlertsService } from "./alerts/performDeploymentAlerts.server";
import { type PrismaClientOrTransaction } from "~/db.server";
import { workerQueue } from "~/services/worker.server";
import { DeploymentService } from "./deployment.server";
import { recordDeploymentOutcome } from "./recordDeploymentOutcome.server";

export class TimeoutDeploymentService extends BaseService {
  public async call(id: string, fromStatus: string, errorMessage: string) {
    const deployment = await this._prisma.workerDeployment.findFirst({
      where: {
        id,
      },
      include: {
        environment: {
          include: {
            project: true,
          },
        },
      },
    });

    if (!deployment) {
      logger.error(`No worker deployment with this ID: ${id}`);
      return;
    }

    if (deployment.status !== fromStatus) {
      // Race: timeout job fired after the deployment moved out of the
      // expected state (already deployed/failed). System handles it by
      // returning early — not an error.
      logger.warn("Deployment is not in the correct state to be timed out", {
        currentStatus: deployment.status,
        fromStatus,
      });
      return;
    }

    const timedOutDeployment = await this._prisma.workerDeployment.update({
      where: {
        id: deployment.id,
      },
      data: {
        status: "TIMED_OUT",
        failedAt: new Date(),
        errorData: { message: errorMessage, name: "TimeoutError" },
      },
    });

    recordDeploymentOutcome({
      status: "TIMED_OUT",
      deploymentFriendlyId: deployment.friendlyId,
      organizationId: deployment.environment.project.organizationId,
      projectId: deployment.environment.projectId,
      environmentId: deployment.environmentId,
      environmentType: deployment.environment.type,
      reason: errorMessage,
    });

    const deploymentService = new DeploymentService();
    await deploymentService
      .appendToEventLog(deployment.environment.project, timedOutDeployment, [
        {
          type: "finalized",
          data: {
            result: "timed_out",
            message: errorMessage,
          },
        },
      ])
      .orTee((error) => {
        logger.error("Failed to append timed out deployment event to event log", { error });
      });

    await PerformDeploymentAlertsService.enqueue(deployment.id);
  }

  static async enqueue(
    deploymentId: string,
    fromStatus: string,
    errorMessage: string,
    runAt: Date
  ) {
    await commonWorker.enqueue({
      id: `timeoutDeployment:${deploymentId}`,
      job: "v3.timeoutDeployment",
      payload: {
        deploymentId,
        fromStatus,
        errorMessage,
      },
      availableAt: runAt,
    });
  }

  static async dequeue(deploymentId: string, tx?: PrismaClientOrTransaction) {
    // For backwards compatibility during transition, we need to dequeue/ack from both workers
    await workerQueue.dequeue(`timeoutDeployment:${deploymentId}`, { tx });
    await commonWorker.ack(`timeoutDeployment:${deploymentId}`);
  }
}
