import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveSandboxConfigForAgent } from "./config.js";
import {
  formatSandboxToolPolicyBlockedMessage,
  resolveSandboxRuntimeStatus,
} from "./runtime-status.js";
import { isToolAllowed, resolveSandboxToolPolicyForAgent } from "./tool-policy.js";

describe("sandbox/tool-policy", () => {
  it("merges sandbox alsoAllow into the default sandbox allowlist", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
        list: [
          {
            id: "tavern",
            tools: {
              sandbox: {
                tools: {
                  alsoAllow: ["message", "tts"],
                },
              },
            },
          },
        ],
      },
    };

    const resolved = resolveSandboxToolPolicyForAgent(cfg, "tavern");
    expect(resolved.allow).toContain("message");
    expect(resolved.allow).toContain("tts");
    expect(resolved.sources.allow).toEqual({
      source: "agent",
      key: "agents.list[].tools.sandbox.tools.alsoAllow",
    });
  });

  it("lets explicit sandbox allow remove entries from the default sandbox denylist", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            allow: ["browser"],
          },
        },
      },
    };

    const resolved = resolveSandboxToolPolicyForAgent(cfg, "main");
    expect(resolved.allow).toContain("browser");
    expect(resolved.deny).not.toContain("browser");
    expect(
      isToolAllowed(
        {
          allow: resolved.allow,
          deny: resolved.deny,
        },
        "browser",
      ),
    ).toBe(true);
  });

  it("preserves allow-all semantics for allow: [] plus alsoAllow", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            allow: [],
            alsoAllow: ["browser"],
          },
        },
      },
    };

    const resolved = resolveSandboxToolPolicyForAgent(cfg, "main");
    expect(resolved.allow).toEqual([]);
    expect(resolved.deny).not.toContain("browser");
    expect(
      isToolAllowed(
        {
          allow: resolved.allow,
          deny: resolved.deny,
        },
        "read",
      ),
    ).toBe(true);
    expect(
      isToolAllowed(
        {
          allow: resolved.allow,
          deny: resolved.deny,
        },
        "browser",
      ),
    ).toBe(true);
  });

  it("keeps canonical sandbox config and runtime status aligned with the effective resolver", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
        list: [
          {
            id: "tavern",
            tools: {
              sandbox: {
                tools: {
                  alsoAllow: ["message", "tts"],
                },
              },
            },
          },
        ],
      },
      tools: {
        sandbox: {
          tools: {
            allow: ["browser"],
          },
        },
      },
    };

    const sandbox = resolveSandboxConfigForAgent(cfg, "tavern");
    expect(sandbox.tools.allow).toEqual(expect.arrayContaining(["browser", "message", "tts"]));
    expect(sandbox.tools.deny).not.toContain("browser");

    const runtime = resolveSandboxRuntimeStatus({
      cfg,
      sessionKey: "agent:tavern:main",
    });
    expect(runtime.toolPolicy.allow).toEqual(expect.arrayContaining(["browser", "message", "tts"]));
    expect(runtime.toolPolicy.deny).not.toContain("browser");
  });

  it("keeps explicit sandbox deny precedence over allow and alsoAllow", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            allow: ["browser"],
            alsoAllow: ["message"],
            deny: ["browser", "message"],
          },
        },
      },
    };

    const resolved = resolveSandboxToolPolicyForAgent(cfg, "main");
    expect(resolved.deny).toContain("browser");
    expect(resolved.deny).toContain("message");
    expect(
      isToolAllowed(
        {
          allow: resolved.allow,
          deny: resolved.deny,
        },
        "browser",
      ),
    ).toBe(false);
    expect(
      isToolAllowed(
        {
          allow: resolved.allow,
          deny: resolved.deny,
        },
        "message",
      ),
    ).toBe(false);
  });

  it("uses the effective sandbox policy when formatting blocked-tool guidance", () => {
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            alsoAllow: ["message"],
          },
        },
      },
    };

    const browserMessage = formatSandboxToolPolicyBlockedMessage({
      cfg,
      sessionKey: "agent:main:main",
      toolName: "browser",
    });
    expect(browserMessage).toContain('Tool "browser" blocked by sandbox tool policy');
    expect(browserMessage).toContain("tools.sandbox.tools.deny");

    const messageToolMessage = formatSandboxToolPolicyBlockedMessage({
      cfg,
      sessionKey: "agent:main:main",
      toolName: "message",
    });
    expect(messageToolMessage).toBeUndefined();
  });

  it("keeps blocked-tool guidance glob-aware and shell-safe", () => {
    const sessionKey = "agent:main:weird session;rm -rf /";
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            deny: ["WEB_*"],
          },
        },
      },
    };

    const message = formatSandboxToolPolicyBlockedMessage({
      cfg,
      sessionKey,
      toolName: "web_fetch",
    });

    expect(message).toContain('Tool "web_fetch" blocked by sandbox tool policy');
    expect(message).toContain("tools.sandbox.tools.deny");
    expect(message).not.toContain(`Session: ${sessionKey}`);
    expect(message).toContain("Session: agent:… -rf /");
    expect(message).toContain(
      "openclaw sandbox explain --session 'agent:main:weird session;rm -rf /'",
    );
  });

  it("avoids terminal injection for control-character session keys", () => {
    const sessionKey = "agent:main:abcde\n12345";
    const cfg: OpenClawConfig = {
      agents: {
        defaults: {
          sandbox: { mode: "all", scope: "agent" },
        },
      },
      tools: {
        sandbox: {
          tools: {
            deny: ["browser"],
          },
        },
      },
    };

    const message = formatSandboxToolPolicyBlockedMessage({
      cfg,
      sessionKey,
      toolName: "browser",
    });

    const sessionLine = message?.split("\n").find((line) => line.startsWith("Session: "));
    expect(sessionLine).toBeDefined();
    expect(sessionLine).not.toContain(sessionKey);
    expect(sessionLine).toContain("\\n");
    expect(message).toContain("openclaw sandbox explain --agent main");
    expect(message).not.toContain("--session");
  });
});
