// The cloud boot recipe — ONE definition shared by the vitest globalsetup
// (ephemeral) and the dev CLI (persistent): WorkOS + Autumn EMULATORS in this
// process plus the app's own dev stack (PGlite dev-db + vite dev) pointed at
// them. The app runs its REAL auth/billing code — real SDKs, real
// sealed-session crypto, real JWKS — against emulated services.
import { rmSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";

// Vendored fork import (same pattern as mcporter).
import { createEmulator } from "@executor-js/emulate";

import { bootProcesses, waitForHttp } from "./boot";
import { AUTUMN_PLAN_SEED } from "./autumn-plans";
import { E2E_EXECUTION_RATE_LIMIT } from "./execution-limits";

export const cloudDir = fileURLToPath(new URL("../../apps/cloud/", import.meta.url));

export interface CloudBootOptions {
  readonly cloudPort: number;
  readonly dbPort: number;
  readonly workosPort: number;
  readonly autumnPort: number;
  readonly workosClientId: string;
  readonly cookiePassword: string;
  /** The URL the app advertises (VITE_PUBLIC_SITE_URL, MCP resource origin). */
  readonly publicUrl: string;
  /**
   * The WorkOS origin the BROWSER must reach (authorize page redirects).
   * Defaults to the emulator's loopback URL; set it when a proxy (e.g.
   * `tailscale serve` HTTPS) fronts the emulator — the app's auth cookies
   * are Secure, so off-localhost access needs https on both sides.
   */
  readonly workosPublicUrl?: string;
  /** vite --host. Default 127.0.0.1. */
  readonly host?: string;
  /** Wipe the dev DB before boot (hermetic). Default true. */
  readonly fresh?: boolean;
  readonly logFile?: string;
  /** Extra env for the app's dev stack (e.g. the suite's OTLP exporter). */
  readonly extraEnv?: Record<string, string>;
}

export interface CloudBooted {
  readonly teardown: () => Promise<void>;
  readonly pids: ReadonlyArray<number>;
  readonly workosUrl: string;
  readonly autumnUrl: string;
}

export const bootCloud = async (options: CloudBootOptions): Promise<CloudBooted> => {
  // Fresh dev DB per boot — the WorkOS emulator mints org ids from a
  // per-process counter, so a persisted DB from a previous invocation
  // collides with the new boot's ids (identities land in polluted orgs /
  // org creation 500s).
  const dbPath = resolve(cloudDir, ".e2e-stub-db");
  if (options.fresh ?? true) rmSync(dbPath, { recursive: true, force: true });

  // MCP access tokens minted by the emulator's OAuth server must carry the
  // app's client id as audience (what the resource server verifies).
  process.env.EMULATE_WORKOS_AUDIENCE = options.workosClientId;
  const workos = await createEmulator({
    service: "workos",
    port: options.workosPort,
    ...(options.workosPublicUrl ? { baseUrl: options.workosPublicUrl } : {}),
  });
  const autumn = await createEmulator({
    service: "autumn",
    port: options.autumnPort,
    // Seed the plan catalog so the billing UI (plans, eligibility, trial
    // checkout) has real plans to render. Derived from autumn.config.ts.
    seed: { autumn: { plans: AUTUMN_PLAN_SEED } },
  });

  const workosUrl = options.workosPublicUrl ?? workos.url;
  const env = {
    // Real client, emulated service. The app derives the browser-facing
    // authorize URL from WORKOS_API_URL, so it must be the PUBLIC origin.
    WORKOS_API_URL: workosUrl,
    AUTUMN_API_URL: autumn.url,
    WORKOS_API_KEY: "sk_test_emulate",
    WORKOS_CLIENT_ID: options.workosClientId,
    WORKOS_COOKIE_PASSWORD: options.cookiePassword,
    AUTUMN_SECRET_KEY: "am_test_emulate",
    ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    DATABASE_URL: `postgresql://postgres:postgres@127.0.0.1:${options.dbPort}/postgres`,
    EXECUTOR_DIRECT_DATABASE_URL: "true",
    CLOUDFLARE_INCLUDE_PROCESS_ENV: "true",
    VITE_PUBLIC_SITE_URL: options.publicUrl,
    // The AuthKit domain (MCP OAuth metadata + JWKS) is the emulator too.
    MCP_AUTHKIT_DOMAIN: workosUrl,
    MCP_RESOURCE_ORIGIN: options.publicUrl,
    MCP_SESSION_TIMEOUT_MS: process.env.MCP_SESSION_TIMEOUT_MS,
    MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: process.env.MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS,
    ALLOW_LOCAL_NETWORK: "true",
    // Shrink the per-org hourly execution cap (prod default 1000) to a number
    // the rate-limit-backstop scenario can actually exhaust with real
    // executions — but see execution-limits.ts: it must stay above every other
    // scenario's per-org execute count. Reaches the worker via
    // CLOUDFLARE_INCLUDE_PROCESS_ENV, same as ALLOW_LOCAL_NETWORK.
    EXECUTION_RATE_LIMIT_PER_HOUR: String(E2E_EXECUTION_RATE_LIMIT),
    // Throwaway PGlite on its own port + dir so it never fights `bun dev`.
    DEV_DB_PORT: String(options.dbPort),
    DEV_DB_PATH: dbPath,
    // Vite rejects unknown Host headers; allow the public hostname when a
    // proxy fronts the app.
    __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: new URL(options.publicUrl).hostname,
    ...options.extraEnv,
  };

  const procs = bootProcesses(
    [
      {
        cmd: "bun",
        args: ["run", "scripts/dev-db.ts"],
        cwd: cloudDir,
        env,
        logFile: options.logFile,
      },
      {
        cmd: "bunx",
        args: [
          "vite",
          "dev",
          "--port",
          String(options.cloudPort),
          "--strictPort",
          "--host",
          options.host ?? "127.0.0.1",
        ],
        cwd: cloudDir,
        // Mitigation, not a fix (the real per-request OOM growth is what the
        // spec-compile LRU addresses): CI runners have 7GB, well above the
        // default ~2GB V8 old-space ceiling, so give the dev server room
        // before it hits the limit and starts 500ing. Scoped to this child
        // only; dev-db does not need it.
        env: {
          ...env,
          NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ""} --max-old-space-size=3072`.trim(),
        },
        logFile: options.logFile,
      },
    ],
    { label: "cloud" },
  );

  const teardown = async () => {
    await procs.teardown();
    await workos.close();
    await autumn.close();
  };

  try {
    const local = `http://127.0.0.1:${options.cloudPort}`;
    await waitForHttp(local);
    // The API plane is ready when login actually redirects to AuthKit.
    await waitForHttp(`${local}/api/auth/login`, { expectRedirect: true });
  } catch (error) {
    await teardown();
    throw error;
  }
  return { teardown, pids: procs.pids, workosUrl, autumnUrl: autumn.url };
};
