import { json } from "@remix-run/server-runtime";
import type { RuntimeEnvironmentType } from "@trigger.dev/database";
import { isUserActorToken } from "@trigger.dev/rbac";
import { rbac } from "~/services/rbac.server";

type EnvironmentScopedResource = "envvars" | "apiKeys";

const RESOURCE_LABELS: Record<EnvironmentScopedResource, string> = {
  envvars: "environment variables",
  apiKeys: "API keys",
};

/**
 * Env-tier RBAC for environment-scoped API routes (env vars, and the endpoints
 * that hand out an environment's secret credentials).
 *
 * Machine credentials (an environment's secret/public API key) are already
 * scoped to a single environment, so they pass through unchanged. A personal
 * access token (or a delegated user-actor token) carries a user, so enforce
 * that user's role for the targeted environment tier — e.g. a Developer can't
 * read deployed env vars or API keys via the API, matching the dashboard
 * restriction. Blocking the credential read for deployed tiers is also what
 * stops a restricted role deploying via the CLI (deploy needs the
 * environment's secret key).
 *
 * Returns a `Response` to short-circuit with when access is denied, or
 * `undefined` when the request may proceed.
 */
export async function authorizePatEnvironmentAccess({
  request,
  authType,
  organizationId,
  projectId,
  envType,
  resource,
  action,
}: {
  request: Request;
  authType: "personalAccessToken" | "organizationAccessToken" | "apiKey";
  organizationId: string;
  projectId: string;
  envType: RuntimeEnvironmentType;
  resource: EnvironmentScopedResource;
  action: "read" | "write";
}): Promise<Response | undefined> {
  const bearer = request.headers
    .get("Authorization")
    ?.replace(/^Bearer /, "")
    .trim();
  const isUat = !!bearer && isUserActorToken(bearer);

  // Machine creds (apiKey) and org tokens carry no user role to enforce. A
  // user-actor token carries a user just like a PAT, so it's gated too.
  if (authType !== "personalAccessToken" && !isUat) {
    return undefined;
  }

  const userAuth = isUat
    ? await rbac.authenticateUserActor(request, { organizationId, projectId })
    : await rbac.authenticatePat(request, { organizationId, projectId });
  if (!userAuth.ok) {
    return json({ error: userAuth.error }, { status: userAuth.status });
  }

  if (!userAuth.ability.can(action, { type: resource, envType })) {
    return json(
      {
        error: `You don't have permission to access this environment's ${RESOURCE_LABELS[resource]}.`,
      },
      { status: 403 }
    );
  }

  return undefined;
}

/** Env-tier env var access for the env var API routes. */
export function authorizeEnvVarApiRequest(opts: {
  request: Request;
  authType: "personalAccessToken" | "organizationAccessToken" | "apiKey";
  organizationId: string;
  projectId: string;
  envType: RuntimeEnvironmentType;
  action: "read" | "write";
}): Promise<Response | undefined> {
  return authorizePatEnvironmentAccess({ ...opts, resource: "envvars" });
}
