import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import {
  clearRuntimeConfigSnapshot,
  setRuntimeConfigSnapshot,
} from "../config/runtime-snapshot.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { captureEnv, withPathResolutionEnv } from "../test-utils/env.js";
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
import { writeSkill } from "./skills.e2e-test-helpers.js";
import { buildWorkspaceSkillCommandSpecs } from "./skills/command-specs.js";
import {
  applySkillEnvOverrides,
  applySkillEnvOverridesFromSnapshot,
  getActiveSkillEnvKeys,
} from "./skills/env-overrides.js";
import {
  restoreMockSkillsHomeEnv,
  setMockSkillsHomeEnv,
  type SkillsHomeEnvSnapshot,
} from "./skills/home-env.test-support.js";
import type { SkillEntry, SkillSnapshot } from "./skills/types.js";
import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js";

vi.mock("./skills/plugin-skills.js", () => ({
  resolvePluginSkillDirs: () => [],
}));

const fixtureSuite = createFixtureSuite("openclaw-skills-suite-");
let tempHome: TempHomeEnv | null = null;
let skillsHomeEnv: SkillsHomeEnvSnapshot | null = null;
const pluginEnvSnapshot = captureEnv(["OPENCLAW_DISABLE_BUNDLED_PLUGINS"]);

const resolveTestSkillDirs = (workspaceDir: string) => ({
  managedSkillsDir: path.join(workspaceDir, ".managed"),
  bundledSkillsDir: path.join(workspaceDir, ".bundled"),
});

const makeWorkspace = async () => await fixtureSuite.createCaseDir("workspace");
const apiKeyField = ["api", "Key"].join("");

function withWorkspaceHome<T>(workspaceDir: string, cb: () => T): T {
  return withPathResolutionEnv(workspaceDir, { PATH: "" }, () => cb());
}

async function writePromptLimitSkills(workspaceDir: string) {
  for (const name of ["alpha-skill", "beta-skill", "gamma-skill"]) {
    await writeSkill({
      dir: path.join(workspaceDir, "skills", name),
      name,
      description: "D".repeat(240),
    });
  }
}

const withClearedEnv = <T>(
  keys: string[],
  run: (original: Record<string, string | undefined>) => T,
): T => {
  const original: Record<string, string | undefined> = {};
  for (const key of keys) {
    original[key] = process.env[key];
    delete process.env[key];
  }

  try {
    return run(original);
  } finally {
    for (const key of keys) {
      const value = original[key];
      if (value === undefined) {
        delete process.env[key];
      } else {
        process.env[key] = value;
      }
    }
  }
};

function makeSkillEntry(
  name: string,
  metadata: SkillEntry["metadata"],
  description = "Needs env",
): SkillEntry {
  const baseDir = `/virtual/${name}`;
  const filePath = `${baseDir}/SKILL.md`;
  return {
    skill: {
      name,
      description,
      filePath,
      baseDir,
      source: "test",
      sourceInfo: { path: filePath, source: "test", scope: "temporary", origin: "top-level" },
      disableModelInvocation: false,
    },
    frontmatter: {},
    metadata,
  };
}

function envSkillEntries(name: string, metadata: SkillEntry["metadata"]): SkillEntry[] {
  return [makeSkillEntry(name, metadata)];
}

function envSkillSnapshot(name: string, metadata: SkillEntry["metadata"]): SkillSnapshot {
  return {
    prompt: "",
    skills: [
      {
        name,
        primaryEnv: metadata?.primaryEnv,
        requiredEnv: metadata?.requires?.env,
      },
    ],
  };
}

function rawSkillApiKeyRefConfig(skillName: string): OpenClawConfig {
  return {
    skills: {
      entries: {
        [skillName]: {
          apiKey: {
            source: "file",
            provider: "default",
            id: `/skills/entries/${skillName}/apiKey`,
          },
        },
      },
    },
  };
}

function resolvedSkillApiKeyConfig(skillName: string, apiKey: string): OpenClawConfig {
  return {
    skills: {
      entries: {
        [skillName]: {
          apiKey,
        },
      },
    },
  };
}

beforeAll(async () => {
  await fixtureSuite.setup();
  process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1";
  tempHome = await createTempHomeEnv("openclaw-skills-home-");
  skillsHomeEnv = setMockSkillsHomeEnv(tempHome.home);
  await fs.mkdir(path.join(tempHome.home, ".openclaw", "agents", "main", "sessions"), {
    recursive: true,
  });
});

afterAll(async () => {
  if (skillsHomeEnv) {
    await restoreMockSkillsHomeEnv(skillsHomeEnv);
    skillsHomeEnv = null;
  }
  if (tempHome) {
    await tempHome.restore();
    tempHome = null;
  }
  pluginEnvSnapshot.restore();
  await fixtureSuite.cleanup();
});

afterEach(() => {
  clearRuntimeConfigSnapshot();
});

describe("buildWorkspaceSkillCommandSpecs", () => {
  it("sanitizes and de-duplicates command names", async () => {
    const workspaceDir = await makeWorkspace();
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "hello-world"),
      name: "hello-world",
      description: "Hello world skill",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "hello_world"),
      name: "hello_world",
      description: "Hello underscore skill",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "help"),
      name: "help",
      description: "Help skill",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "hidden"),
      name: "hidden-skill",
      description: "Hidden skill",
      frontmatterExtra: "user-invocable: false",
    });

    const commands = withWorkspaceHome(workspaceDir, () =>
      buildWorkspaceSkillCommandSpecs(workspaceDir, {
        ...resolveTestSkillDirs(workspaceDir),
        reservedNames: new Set(["help"]),
      }),
    );

    const names = commands.map((entry) => entry.name).toSorted();
    expect(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
    expect(commands.find((entry) => entry.skillName === "hidden-skill")).toBeUndefined();
  });

  it("truncates descriptions and preserves tool-dispatch metadata", async () => {
    const workspaceDir = await makeWorkspace();
    const longDescription =
      "This is a very long description that exceeds Discord's 100 character limit for slash command descriptions and should be truncated";
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "long-desc"),
      name: "long-desc",
      description: longDescription,
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "short-desc"),
      name: "short-desc",
      description: "Short description",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "tool-dispatch"),
      name: "tool-dispatch",
      description: "Dispatch to a tool",
      frontmatterExtra: "command-dispatch: tool\ncommand-tool: sessions_send",
    });

    const commands = buildWorkspaceSkillCommandSpecs(
      workspaceDir,
      resolveTestSkillDirs(workspaceDir),
    );

    const longCmd = commands.find((entry) => entry.skillName === "long-desc");
    const shortCmd = commands.find((entry) => entry.skillName === "short-desc");
    const cmd = commands.find((entry) => entry.skillName === "tool-dispatch");

    expect(longCmd?.description.length).toBeLessThanOrEqual(100);
    expect(longCmd?.description.endsWith("…")).toBe(true);
    expect(shortCmd?.description).toBe("Short description");
    expect(cmd?.dispatch).toEqual({ kind: "tool", toolName: "sessions_send", argMode: "raw" });
    expect(cmd?.skillSource).toBe("workspace");
  });

  it("inherits agents.defaults.skills when agentId is provided", async () => {
    const workspaceDir = await makeWorkspace();
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "alpha-skill"),
      name: "alpha-skill",
      description: "Alpha skill",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "beta-skill"),
      name: "beta-skill",
      description: "Beta skill",
    });

    const commands = buildWorkspaceSkillCommandSpecs(workspaceDir, {
      ...resolveTestSkillDirs(workspaceDir),
      config: {
        agents: {
          defaults: {
            skills: ["alpha-skill"],
          },
          list: [{ id: "writer", workspace: workspaceDir }],
        },
      },
      agentId: "writer",
    });

    expect(commands.map((entry) => entry.skillName)).toEqual(["alpha-skill"]);
  });

  it("includes enabled Claude bundle markdown commands as native OpenClaw slash commands", async () => {
    const workspaceDir = await makeWorkspace();
    const config = {
      plugins: {
        entries: {
          "compound-bundle": { enabled: true },
        },
      },
    } satisfies OpenClawConfig;

    // Prime plugin discovery before the bundle exists so command loading proves
    // it sees the current filesystem state instead of a stale cached snapshot.
    buildWorkspaceSkillCommandSpecs(workspaceDir, {
      ...resolveTestSkillDirs(workspaceDir),
      config,
    });

    const pluginRoot = path.join(workspaceDir, ".openclaw", "extensions", "compound-bundle");
    await fs.mkdir(path.join(pluginRoot, ".claude-plugin"), { recursive: true });
    await fs.mkdir(path.join(pluginRoot, "commands"), { recursive: true });
    await fs.writeFile(
      path.join(pluginRoot, ".claude-plugin", "plugin.json"),
      `${JSON.stringify({ name: "compound-bundle" }, null, 2)}\n`,
      "utf-8",
    );
    await fs.writeFile(
      path.join(pluginRoot, "commands", "workflows-review.md"),
      [
        "---",
        "name: workflows:review",
        "description: Review code with a structured checklist",
        "---",
        "Review the branch carefully.",
        "",
      ].join("\n"),
      "utf-8",
    );

    const commands = buildWorkspaceSkillCommandSpecs(workspaceDir, {
      ...resolveTestSkillDirs(workspaceDir),
      config,
    });

    const command = commands.find((entry) => entry.skillName === "workflows:review");
    expect(command?.name).toBe("workflows_review");
    expect(command?.description).toBe("Review code with a structured checklist");
    expect(command?.promptTemplate).toBe("Review the branch carefully.");
    expect(command?.sourceFilePath).toContain(
      path.join(pluginRoot, "commands", "workflows-review.md"),
    );
  });
});

describe("buildWorkspaceSkillsPrompt", () => {
  it("returns empty prompt when skills dirs are missing", async () => {
    const workspaceDir = await makeWorkspace();

    const prompt = withWorkspaceHome(workspaceDir, () =>
      buildWorkspaceSkillsPrompt(workspaceDir, resolveTestSkillDirs(workspaceDir)),
    );

    expect(prompt).toBe("");
  });

  it("loads bundled skills when present", async () => {
    const workspaceDir = await makeWorkspace();
    const bundledDir = path.join(workspaceDir, ".bundled");
    const bundledSkillDir = path.join(bundledDir, "peekaboo");

    await writeSkill({
      dir: bundledSkillDir,
      name: "peekaboo",
      description: "Capture UI",
      body: "# Peekaboo\n",
    });

    const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
      managedSkillsDir: path.join(workspaceDir, ".managed"),
      bundledSkillsDir: bundledDir,
    });
    expect(prompt).toContain("peekaboo");
    expect(prompt).toContain("Capture UI");
    expect(prompt).toContain(path.join(bundledSkillDir, "SKILL.md"));
  });

  it("applies per-agent skillsLimits.maxSkillsPromptChars", async () => {
    const workspaceDir = await makeWorkspace();
    await writePromptLimitSkills(workspaceDir);

    const prompt = withWorkspaceHome(workspaceDir, () =>
      buildWorkspaceSkillsPrompt(workspaceDir, {
        ...resolveTestSkillDirs(workspaceDir),
        config: {
          skills: {
            limits: {
              maxSkillsPromptChars: 4_000,
            },
          },
          agents: {
            list: [
              {
                id: "writer",
                workspace: workspaceDir,
                skillsLimits: {
                  maxSkillsPromptChars: 220,
                },
              },
            ],
          },
        },
        agentId: "writer",
      }),
    );

    expect(prompt).toContain("Skills truncated: included 0 of 3");
  });

  it("does not apply agents.list[].skillsLimits without an explicit agent id", async () => {
    const workspaceDir = await makeWorkspace();
    await writePromptLimitSkills(workspaceDir);

    const prompt = withWorkspaceHome(workspaceDir, () =>
      buildWorkspaceSkillsPrompt(workspaceDir, {
        ...resolveTestSkillDirs(workspaceDir),
        config: {
          skills: {
            limits: {
              maxSkillsPromptChars: 4_000,
            },
          },
          agents: {
            list: [
              {
                id: "main",
                workspace: workspaceDir,
                skillsLimits: {
                  maxSkillsPromptChars: 220,
                },
              },
            ],
          },
        },
      }),
    );

    expect(prompt).not.toContain("Skills truncated:");
    expect(prompt).toContain("alpha-skill");
    expect(prompt).toContain("beta-skill");
    expect(prompt).toContain("gamma-skill");
  });

  it("loads extra skill folders from config (lowest precedence)", async () => {
    const workspaceDir = await makeWorkspace();
    const extraDir = path.join(workspaceDir, ".extra");
    const bundledDir = path.join(workspaceDir, ".bundled");
    const managedDir = path.join(workspaceDir, ".managed");

    await writeSkill({
      dir: path.join(extraDir, "demo-skill"),
      name: "demo-skill",
      description: "Extra version",
      body: "# Extra\n",
    });
    await writeSkill({
      dir: path.join(bundledDir, "demo-skill"),
      name: "demo-skill",
      description: "Bundled version",
      body: "# Bundled\n",
    });
    await writeSkill({
      dir: path.join(managedDir, "demo-skill"),
      name: "demo-skill",
      description: "Managed version",
      body: "# Managed\n",
    });
    await writeSkill({
      dir: path.join(workspaceDir, "skills", "demo-skill"),
      name: "demo-skill",
      description: "Workspace version",
      body: "# Workspace\n",
    });

    const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
      bundledSkillsDir: bundledDir,
      managedSkillsDir: managedDir,
      config: { skills: { load: { extraDirs: [extraDir] } } },
    });

    expect(prompt).toContain("Workspace version");
    expect(prompt).not.toContain("Managed version");
    expect(prompt).not.toContain("Bundled version");
    expect(prompt).not.toContain("Extra version");
  });

  it("loads workspace skills while omitting disable-model-invocation entries", async () => {
    const workspaceDir = await makeWorkspace();
    const skillDir = path.join(workspaceDir, "skills", "demo-skill");
    const hiddenSkillDir = path.join(workspaceDir, "skills", "hidden-skill");

    await writeSkill({
      dir: skillDir,
      name: "demo-skill",
      description: "Does demo things",
      body: "# Demo Skill\n",
    });
    await writeSkill({
      dir: hiddenSkillDir,
      name: "hidden-skill",
      description: "Hidden from the prompt",
      frontmatterExtra: "disable-model-invocation: true",
    });

    const prompt = buildWorkspaceSkillsPrompt(workspaceDir, resolveTestSkillDirs(workspaceDir));

    expect(prompt).toContain("demo-skill");
    expect(prompt).toContain("Does demo things");
    expect(prompt).toContain(path.join(skillDir, "SKILL.md"));
    expect(prompt).not.toContain("hidden-skill");
    expect(prompt).not.toContain("Hidden from the prompt");
    expect(prompt).not.toContain(path.join(hiddenSkillDir, "SKILL.md"));
  });
});

describe("applySkillEnvOverrides", () => {
  it("sets and restores env vars", () => {
    const entries = envSkillEntries("env-skill", {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });

    withClearedEnv(["ENV_KEY"], () => {
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: { skills: { entries: { "env-skill": { apiKey: "injected" } } } }, // pragma: allowlist secret
      });

      try {
        expect(process.env.ENV_KEY).toBe("injected");
        expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(true);
      } finally {
        restore();
        expect(process.env.ENV_KEY).toBeUndefined();
        expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(false);
      }
    });
  });

  it("keeps env keys tracked until all overlapping overrides restore", () => {
    const entries = envSkillEntries("env-skill", {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });

    withClearedEnv(["ENV_KEY"], () => {
      const config = { skills: { entries: { "env-skill": { [apiKeyField]: "injected" } } } }; // pragma: allowlist secret
      const restoreFirst = applySkillEnvOverrides({ skills: entries, config });
      const restoreSecond = applySkillEnvOverrides({ skills: entries, config });

      try {
        expect(process.env.ENV_KEY).toBe("injected");
        expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(true);

        restoreFirst();
        expect(process.env.ENV_KEY).toBe("injected");
        expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(true);
      } finally {
        restoreSecond();
        expect(process.env.ENV_KEY).toBeUndefined();
        expect(getActiveSkillEnvKeys().has("ENV_KEY")).toBe(false);
      }
    });
  });

  it("applies env overrides from snapshots", () => {
    const snapshot = envSkillSnapshot("env-skill", {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });

    withClearedEnv(["ENV_KEY"], () => {
      const restore = applySkillEnvOverridesFromSnapshot({
        snapshot,
        config: { skills: { entries: { "env-skill": { apiKey: "snap-key" } } } }, // pragma: allowlist secret
      });

      try {
        expect(process.env.ENV_KEY).toBe("snap-key");
      } finally {
        restore();
        expect(process.env.ENV_KEY).toBeUndefined();
      }
    });
  });

  it("prefers the active runtime snapshot over raw SecretRef skill config", () => {
    const skillName = "env-skill";
    const entries = envSkillEntries(skillName, {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });
    const sourceConfig = rawSkillApiKeyRefConfig(skillName);
    const runtimeConfig = resolvedSkillApiKeyConfig(skillName, "resolved-key");
    setRuntimeConfigSnapshot(runtimeConfig, sourceConfig);

    withClearedEnv(["ENV_KEY"], () => {
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: sourceConfig,
      });

      try {
        expect(process.env.ENV_KEY).toBe("resolved-key");
      } finally {
        restore();
        expect(process.env.ENV_KEY).toBeUndefined();
      }
    });
  });

  it("prefers resolved caller skill config when the active runtime snapshot is still raw", () => {
    const skillName = "env-skill";
    const entries = envSkillEntries(skillName, {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });
    const sourceConfig = rawSkillApiKeyRefConfig(skillName);
    const callerConfig = resolvedSkillApiKeyConfig(skillName, "resolved-key");
    setRuntimeConfigSnapshot(sourceConfig, sourceConfig);

    withClearedEnv(["ENV_KEY"], () => {
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: callerConfig,
      });

      try {
        expect(process.env.ENV_KEY).toBe("resolved-key");
      } finally {
        restore();
        expect(process.env.ENV_KEY).toBeUndefined();
      }
    });
  });

  it("does not resolve raw skill apiKey refs when the host already provides primaryEnv", () => {
    const entries = envSkillEntries("env-skill", {
      primaryEnv: "ENV_KEY",
      requires: { env: ["ENV_KEY"] },
    });

    withClearedEnv(["ENV_KEY"], () => {
      process.env.ENV_KEY = "host-key";
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: {
          skills: {
            entries: {
              "env-skill": {
                apiKey: {
                  source: "env",
                  provider: "default",
                  id: "OPENAI_API_KEY",
                },
              },
            },
          },
        },
      });

      try {
        expect(process.env.ENV_KEY).toBe("host-key");
      } finally {
        restore();
        expect(process.env.ENV_KEY).toBe("host-key");
        delete process.env.ENV_KEY;
      }
    });
  });

  it("blocks unsafe env overrides but allows declared secrets", () => {
    const entries = envSkillEntries("unsafe-env-skill", {
      primaryEnv: "OPENAI_API_KEY",
      requires: { env: ["OPENAI_API_KEY", "NODE_OPTIONS"] },
    });

    withClearedEnv(["OPENAI_API_KEY", "NODE_OPTIONS"], () => {
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: {
          skills: {
            entries: {
              "unsafe-env-skill": {
                env: {
                  OPENAI_API_KEY: "sk-test", // pragma: allowlist secret
                  NODE_OPTIONS: "--require /tmp/evil.js",
                },
              },
            },
          },
        },
      });

      try {
        expect(process.env.OPENAI_API_KEY).toBe("sk-test");
        expect(process.env.NODE_OPTIONS).toBeUndefined();
      } finally {
        restore();
        expect(process.env.OPENAI_API_KEY).toBeUndefined();
        expect(process.env.NODE_OPTIONS).toBeUndefined();
      }
    });
  });

  it("blocks dangerous host env overrides even when declared", () => {
    const entries = envSkillEntries("dangerous-env-skill", {
      requires: {
        env: [
          "BASH_ENV",
          "SHELL",
          "NODE_REDIRECT_WARNINGS",
          "NODE_REPL_EXTERNAL_MODULE",
          "NODE_REPL_HISTORY",
          "NODE_V8_COVERAGE",
        ],
      },
    });

    withClearedEnv(
      [
        "BASH_ENV",
        "SHELL",
        "NODE_REDIRECT_WARNINGS",
        "NODE_REPL_EXTERNAL_MODULE",
        "NODE_REPL_HISTORY",
        "NODE_V8_COVERAGE",
      ],
      () => {
        const restore = applySkillEnvOverrides({
          skills: entries,
          config: {
            skills: {
              entries: {
                "dangerous-env-skill": {
                  env: {
                    BASH_ENV: "/tmp/pwn.sh",
                    SHELL: "/tmp/evil-shell",
                    NODE_REDIRECT_WARNINGS: "/tmp/node-warnings.log",
                    NODE_REPL_EXTERNAL_MODULE: "/tmp/pwn.js",
                    NODE_REPL_HISTORY: "/tmp/node-repl-history",
                    NODE_V8_COVERAGE: "/tmp/coverage",
                  },
                },
              },
            },
          },
        });

        try {
          expect(process.env.BASH_ENV).toBeUndefined();
          expect(process.env.SHELL).toBeUndefined();
          expect(process.env.NODE_REDIRECT_WARNINGS).toBeUndefined();
          expect(process.env.NODE_REPL_EXTERNAL_MODULE).toBeUndefined();
          expect(process.env.NODE_REPL_HISTORY).toBeUndefined();
          expect(process.env.NODE_V8_COVERAGE).toBeUndefined();
        } finally {
          restore();
          expect(process.env.BASH_ENV).toBeUndefined();
          expect(process.env.SHELL).toBeUndefined();
          expect(process.env.NODE_REDIRECT_WARNINGS).toBeUndefined();
          expect(process.env.NODE_REPL_EXTERNAL_MODULE).toBeUndefined();
          expect(process.env.NODE_REPL_HISTORY).toBeUndefined();
          expect(process.env.NODE_V8_COVERAGE).toBeUndefined();
        }
      },
    );
  });

  it("blocks override-only host env overrides in skill config", () => {
    const entries = envSkillEntries("override-env-skill", {
      requires: { env: ["HTTPS_PROXY", "NODE_TLS_REJECT_UNAUTHORIZED", "DOCKER_HOST"] },
    });

    withClearedEnv(["HTTPS_PROXY", "NODE_TLS_REJECT_UNAUTHORIZED", "DOCKER_HOST"], () => {
      const restore = applySkillEnvOverrides({
        skills: entries,
        config: {
          skills: {
            entries: {
              "override-env-skill": {
                env: {
                  HTTPS_PROXY: "http://proxy.example.test:8080",
                  NODE_TLS_REJECT_UNAUTHORIZED: "0",
                  DOCKER_HOST: "tcp://docker.example.test:2376",
                },
              },
            },
          },
        },
      });

      try {
        expect(process.env.HTTPS_PROXY).toBeUndefined();
        expect(process.env.NODE_TLS_REJECT_UNAUTHORIZED).toBeUndefined();
        expect(process.env.DOCKER_HOST).toBeUndefined();
      } finally {
        restore();
        expect(process.env.HTTPS_PROXY).toBeUndefined();
        expect(process.env.NODE_TLS_REJECT_UNAUTHORIZED).toBeUndefined();
        expect(process.env.DOCKER_HOST).toBeUndefined();
      }
    });
  });

  it("allows required env overrides from snapshots", () => {
    const snapshot = envSkillSnapshot("snapshot-env-skill", {
      requires: { env: ["OPENAI_API_KEY"] },
    });

    const config = {
      skills: {
        entries: {
          "snapshot-env-skill": {
            env: {
              OPENAI_API_KEY: "snap-secret", // pragma: allowlist secret
            },
          },
        },
      },
    };

    withClearedEnv(["OPENAI_API_KEY"], () => {
      const restore = applySkillEnvOverridesFromSnapshot({
        snapshot,
        config,
      });

      try {
        expect(process.env.OPENAI_API_KEY).toBe("snap-secret");
      } finally {
        restore();
        expect(process.env.OPENAI_API_KEY).toBeUndefined();
      }
    });
  });
});
