import { tryCatch } from "@trigger.dev/core";
import { ManageConcurrencyPresenter } from "~/presenters/v3/ManageConcurrencyPresenter.server";
import { BaseService } from "./baseService.server";
import { updateEnvConcurrencyLimits } from "../runQueue.server";
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";

type Input = {
  userId: string;
  projectId: string;
  organizationId: string;
  environments: { id: string; amount: number }[];
};

type Result =
  | {
      success: true;
    }
  | {
      success: false;
      error: string;
    };

export class AllocateConcurrencyService extends BaseService {
  async call({ userId, projectId, organizationId, environments }: Input): Promise<Result> {
    // fetch the current concurrency
    const presenter = new ManageConcurrencyPresenter(this._prisma, this._replica);
    const [error, result] = await tryCatch(
      presenter.call({
        userId,
        projectId,
        organizationId,
      })
    );

    if (error) {
      return {
        success: false,
        error: "Unknown error",
      };
    }

    const previousExtra = result.environments.reduce(
      (acc, e) => Math.max(0, e.maximumConcurrencyLimit - e.planConcurrencyLimit) + acc,
      0
    );
    const requested = new Map(environments.map((e) => [e.id, e.amount]));
    const newExtra = result.environments.reduce((acc, env) => {
      const targetExtra = requested.has(env.id)
        ? Math.max(0, requested.get(env.id)!)
        : Math.max(0, env.maximumConcurrencyLimit - env.planConcurrencyLimit);
      return acc + targetExtra;
    }, 0);
    const change = newExtra - previousExtra;

    const totalExtra = result.extraAllocatedConcurrency + change;

    if (change > result.extraUnallocatedConcurrency) {
      return {
        success: false,
        error: `You don't have enough unallocated concurrency available. You requested ${totalExtra} but only have ${result.extraUnallocatedConcurrency}.`,
      };
    }

    for (const environment of environments) {
      const existingEnvironment = result.environments.find((e) => e.id === environment.id);

      if (!existingEnvironment) {
        return {
          success: false,
          error: `Environment not found ${environment.id}`,
        };
      }

      const newConcurrency = existingEnvironment.planConcurrencyLimit + environment.amount;

      const updatedEnvironment = await this._prisma.runtimeEnvironment.update({
        where: {
          id: environment.id,
        },
        data: {
          maximumConcurrencyLimit: newConcurrency,
        },
        include: {
          project: true,
          organization: true,
        },
      });

      if (!updatedEnvironment.paused) {
        await updateEnvConcurrencyLimits(updatedEnvironment);
      }

      // maximumConcurrencyLimit changed in the control-plane; drop any cached copy.
      controlPlaneResolver.invalidateEnvironment(environment.id);
    }

    return {
      success: true,
    };
  }
}
