/**
 * # session/harness/vertex-maas — Vertex Model Garden MaaS plumbing (ADC bearer + the region trap) (kestrel-k8he)
 *
 * Vertex AI Model Garden serves the open frontier weights (DeepSeek V3.2, Qwen3-Next-80B, Kimi K2
 * Thinking) **as-a-service on GCP CREDITS**, over an OpenAI-compatible chat-completions endpoint.
 * Live probe (wf_e58255fd-e01, 2026-07-18) verified all three MaaS slugs serve 200 on
 *
 *   `https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/global/endpoints/openapi/chat/completions`
 *
 * with an **ADC bearer** (`gcloud auth application-default print-access-token`) — NOT an API key.
 * This module owns everything about that path that does not touch a provider package (so it stays
 * importable by cost-planning / test code): the base-URL builder, the fail-closed project + location
 * resolution, and the injectable gcloud-ADC token source. The wire-touching client construction
 * (`createOpenAICompatible` + the bearer-attaching fetch) lives in `./ai-sdk-client.ts`, the ONE
 * provider-bearing module.
 *
 * ## THE REGION TRAP (pin location=global, fail LOUDLY on anything else)
 * The same live probe showed `us-central1` rejects EVERY MaaS slug ("Model … is not available in
 * region"); only `location=global` serves them. A harness configured with a region — an
 * `opts.region`, a `GOOGLE_VERTEX_LOCATION`/`GOOGLE_CLOUD_LOCATION` in the shell (both common gcloud
 * conventions) — must therefore FAIL with the clear region error at construction, never silently
 * "helpfully" rewrite the location to global: a silent rewrite would hide that the caller's config
 * disagrees with the wire, and the next person to copy that config onto a raw curl would get the
 * opaque vendor 404 with no breadcrumb. {@link resolveVertexMaasLocation} is that guard.
 *
 * ## DEAD ADC: surface the login, never silently skip (the gcloud auto-reauth doctrine)
 * An ADC bearer expires (~1h) and dies entirely when the grant is revoked. When the refresh
 * subprocess fails, {@link gcloudAdcTokenSource} throws {@link AdcAuthError} carrying the EXACT
 * reauth command (`gcloud auth application-default login`) — the documented doctrine is that an
 * agent RUNS that login (it opens the owner's browser) and waits, and NEVER quietly skips the
 * Vertex lane. What this code must never do is swallow the failure and let a runner tally the seat
 * as "no result" as if the lane did not exist.
 */

import { execFile } from "node:child_process";

/** The ONLY location Vertex Model Garden MaaS serves from (probe wf_e58255fd-e01: us-central1
 * rejects all MaaS slugs). Pinned; a configured non-global location throws — see the module doc. */
export const VERTEX_MAAS_LOCATION = "global";

/** The provider `name` given to `createOpenAICompatible` — also the `providerOptions` key an
 * openai-compatible model reads passthrough options from (same convention as `fireworks`). */
export const VERTEX_MAAS_PROVIDER_NAME = "vertex-maas";

/** The env names a GCP project id may arrive under, in precedence order. `GOOGLE_VERTEX_PROJECT` is
 * the `@ai-sdk/google-vertex` convention; the other two are gcloud's own. */
export const VERTEX_MAAS_PROJECT_ENV_VARS = ["GOOGLE_VERTEX_PROJECT", "GOOGLE_CLOUD_PROJECT", "GCLOUD_PROJECT"] as const;

/** The env names a location/region may leak in from (both gcloud conventions). Read ONLY to fail
 * loudly on the region trap — never to actually route anywhere but {@link VERTEX_MAAS_LOCATION}. */
export const VERTEX_MAAS_LOCATION_ENV_VARS = ["GOOGLE_VERTEX_LOCATION", "GOOGLE_CLOUD_LOCATION"] as const;

/** The GCP project id from the environment (first present, non-trivial value wins), or `undefined`.
 * A project id is an ADDRESS, not a secret — but it is account-specific, so it is never hardcoded. */
export function vertexMaasProjectFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
  for (const name of VERTEX_MAAS_PROJECT_ENV_VARS) {
    const v = env[name];
    if (typeof v === "string" && v.trim() !== "") return v.trim();
  }
  return undefined;
}

/** Fail-closed project resolution: the env project, or a THROW naming the env vars to set. A blank
 * project would 404 opaquely on the wire (the URL embeds it); this error says what to fix instead. */
export function requireVertexMaasProject(env: NodeJS.ProcessEnv = process.env): string {
  const project = vertexMaasProjectFromEnv(env);
  if (project === undefined) {
    throw new Error(
      `vertex-maas: no GCP project id — set one of ${VERTEX_MAAS_PROJECT_ENV_VARS.join(" / ")} ` +
        `(the MaaS endpoint URL embeds the project; refusing to construct a client that can only 404).`,
    );
  }
  return project;
}

/** Raised when a vertex-maas client is configured with a non-global location — the REGION TRAP made
 * loud. Fail-closed: the caller's config disagrees with the only location the wire serves, so the
 * client refuses to construct rather than silently rewriting the location (see the module doc). */
export class VertexMaasRegionError extends Error {
  constructor(configured: string, sourceOfTruth: string) {
    super(
      `vertex-maas: location "${configured}" (from ${sourceOfTruth}) is not available for Vertex Model Garden MaaS — ` +
        `regional endpoints (us-central1 included) reject ALL MaaS model slugs with "not available in region" ` +
        `(live probe wf_e58255fd-e01, 2026-07-18). The ONLY serving location is "${VERTEX_MAAS_LOCATION}". ` +
        `Drop the region override (opts.region / ${VERTEX_MAAS_LOCATION_ENV_VARS.join(" / ")}) or set it to "${VERTEX_MAAS_LOCATION}" — ` +
        `refusing to silently rewrite your configured location.`,
    );
    this.name = "VertexMaasRegionError";
  }
}

/**
 * Resolve the MaaS serving location: always {@link VERTEX_MAAS_LOCATION}, and a THROW
 * ({@link VertexMaasRegionError}) when the caller CONFIGURED anything else — an explicit
 * `configured` (the client's `region` option) first, else a location leaked in from the gcloud env
 * conventions. A `configured`/env value of exactly `"global"` is accepted (it agrees with the pin).
 */
export function resolveVertexMaasLocation(configured?: string, env: NodeJS.ProcessEnv = process.env): string {
  if (configured !== undefined && configured.trim() !== "" && configured.trim() !== VERTEX_MAAS_LOCATION) {
    throw new VertexMaasRegionError(configured.trim(), "the client's region option");
  }
  for (const name of VERTEX_MAAS_LOCATION_ENV_VARS) {
    const v = env[name];
    if (typeof v === "string" && v.trim() !== "" && v.trim() !== VERTEX_MAAS_LOCATION) {
      throw new VertexMaasRegionError(v.trim(), name);
    }
  }
  return VERTEX_MAAS_LOCATION;
}

/** The OpenAI-compatible base URL for a project's MaaS endpoint. `createOpenAICompatible` appends
 * `/chat/completions` — this is everything before it (verified live, probe wf_e58255fd-e01). */
export function vertexMaasBaseUrl(project: string, location: string = VERTEX_MAAS_LOCATION): string {
  return `https://aiplatform.googleapis.com/v1beta1/projects/${project}/locations/${location}/endpoints/openapi`;
}

/** One ADC-refresh subprocess result — the injectable seam, same shape discipline as the codex-cli
 * lane's `CodexExecFn` (tests inject a stand-in; the real one spawns gcloud). */
export interface AdcExecResult {
  readonly stdout: string;
  readonly stderr: string;
  readonly exitCode: number;
}

/** The injectable subprocess seam: run `cmd args…` and hand back its streams + exit code. */
export type AdcExecFn = (cmd: string, args: readonly string[]) => Promise<AdcExecResult>;

/** Raised when the ADC bearer cannot be refreshed — dead/expired Application Default Credentials.
 * The message carries the exact reauth command; per the gcloud auto-reauth doctrine the agent runs
 * it (browser login) and waits — the Vertex lane is NEVER silently skipped. */
export class AdcAuthError extends Error {
  constructor(detail: string) {
    super(
      `vertex-maas: gcloud ADC token refresh failed — ${detail}. ` +
        `Application Default Credentials are dead or absent. Run \`gcloud auth application-default login\` ` +
        `(opens the browser login; wait for it to complete), then retry. Per the gcloud auto-reauth doctrine ` +
        `this lane must SURFACE the login and wait — never silently skip the Vertex seat.`,
    );
    this.name = "AdcAuthError";
  }
}

/** How long a fetched ADC bearer is reused before re-running gcloud (tokens live ~60 min; 45 keeps a
 * comfortable margin so a long fan-out never sends a just-expired bearer). */
export const ADC_TOKEN_TTL_MS = 45 * 60 * 1000;

/** A refreshable ADC bearer-token source. `token()` returns a live bearer (cached within
 * {@link ADC_TOKEN_TTL_MS}); `issued()` lists every token value this source has ever handed out —
 * the belt-and-suspenders feed into `scrubSecrets`, so an ADC bearer (which lives in NO env var and
 * would escape the `CREDENTIAL_ENV_VARS` scrub) can still never reach a wire-evidence artifact. */
export interface AdcTokenSource {
  token(): Promise<string>;
  issued(): readonly string[];
}

/** The REAL subprocess seam: spawn `gcloud auth application-default print-access-token`. */
const realAdcExec: AdcExecFn = (cmd, args) =>
  new Promise((resolve) => {
    execFile(cmd, [...args], { timeout: 30_000 }, (error, stdout, stderr) => {
      const code = (error as NodeJS.ErrnoException | null)?.code;
      const exitCode = error === null ? 0 : typeof code === "number" ? code : 1;
      resolve({ stdout: String(stdout), stderr: String(stderr), exitCode });
    });
  });

/**
 * The gcloud-ADC {@link AdcTokenSource}: refresh via `gcloud auth application-default
 * print-access-token`, cache for {@link ADC_TOKEN_TTL_MS}, and FAIL LOUDLY ({@link AdcAuthError},
 * naming the reauth command) on a dead ADC — never a silent skip. `exec` is the test seam (same
 * pattern as the codex-cli lane); NEVER set on a production client.
 */
export function gcloudAdcTokenSource(opts: { readonly exec?: AdcExecFn; readonly now?: () => number } = {}): AdcTokenSource {
  const exec = opts.exec ?? realAdcExec;
  const now = opts.now ?? Date.now;
  const handedOut: string[] = [];
  let cached: { readonly token: string; readonly at: number } | undefined;
  return {
    async token(): Promise<string> {
      if (cached !== undefined && now() - cached.at < ADC_TOKEN_TTL_MS) return cached.token;
      let r: AdcExecResult;
      try {
        r = await exec("gcloud", ["auth", "application-default", "print-access-token"]);
      } catch (e) {
        throw new AdcAuthError(`could not spawn gcloud (${e instanceof Error ? e.message : String(e)})`);
      }
      const token = r.stdout.trim();
      if (r.exitCode !== 0 || token === "") {
        throw new AdcAuthError(
          `exit ${r.exitCode}${r.stderr.trim() !== "" ? `: ${r.stderr.trim()}` : token === "" ? ": empty token" : ""}`,
        );
      }
      cached = { token, at: now() };
      if (!handedOut.includes(token)) handedOut.push(token);
      return token;
    },
    issued(): readonly string[] {
      return handedOut;
    },
  };
}
