import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { beforeEach, describe, expect, it } from "vitest";
import {
  callGatewayMock,
  resetSubagentsConfigOverride,
  setSubagentsConfigOverride,
} from "./openclaw-tools.subagents.test-harness.js";
import { addSubagentRunForTests, resetSubagentRegistryForTests } from "./subagent-registry.js";
import "./test-helpers/fast-core-tools.js";
import { createPerSenderSessionConfig } from "./test-helpers/session-config.js";
import { createSubagentsTool } from "./tools/subagents-tool.js";

function writeStore(storePath: string, store: Record<string, unknown>) {
  fs.mkdirSync(path.dirname(storePath), { recursive: true });
  fs.writeFileSync(storePath, JSON.stringify(store, null, 2), "utf-8");
}

describe("openclaw-tools: subagents scope isolation", () => {
  let storePath = "";

  beforeEach(() => {
    resetSubagentRegistryForTests();
    resetSubagentsConfigOverride();
    callGatewayMock.mockReset();
    storePath = path.join(
      os.tmpdir(),
      `openclaw-subagents-scope-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
    );
    setSubagentsConfigOverride({
      session: createPerSenderSessionConfig({ store: storePath }),
    });
    writeStore(storePath, {});
  });

  it("leaf subagents do not inherit parent sibling control scope", async () => {
    const leafKey = "agent:main:subagent:leaf";
    const siblingKey = "agent:main:subagent:unsandboxed";

    writeStore(storePath, {
      [leafKey]: {
        sessionId: "leaf-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
      },
      [siblingKey]: {
        sessionId: "sibling-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
      },
    });

    addSubagentRunForTests({
      runId: "run-leaf",
      childSessionKey: leafKey,
      requesterSessionKey: "agent:main:main",
      requesterDisplayKey: "main",
      task: "sandboxed leaf",
      cleanup: "keep",
      createdAt: Date.now() - 30_000,
      startedAt: Date.now() - 30_000,
    });
    addSubagentRunForTests({
      runId: "run-sibling",
      childSessionKey: siblingKey,
      requesterSessionKey: "agent:main:main",
      requesterDisplayKey: "main",
      task: "unsandboxed sibling",
      cleanup: "keep",
      createdAt: Date.now() - 20_000,
      startedAt: Date.now() - 20_000,
    });

    const tool = createSubagentsTool({ agentSessionKey: leafKey });
    const result = await tool.execute("call-leaf-list", { action: "list" });

    expect(result.details).toMatchObject({
      status: "ok",
      requesterSessionKey: leafKey,
      callerSessionKey: leafKey,
      callerIsSubagent: true,
      total: 0,
      active: [],
      recent: [],
    });
    expect(callGatewayMock).not.toHaveBeenCalled();
  });

  it("orchestrator subagents still see children they spawned", async () => {
    const orchestratorKey = "agent:main:subagent:orchestrator";
    const workerKey = `${orchestratorKey}:subagent:worker`;
    const siblingKey = "agent:main:subagent:sibling";

    writeStore(storePath, {
      [orchestratorKey]: {
        sessionId: "orchestrator-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
      },
      [workerKey]: {
        sessionId: "worker-session",
        updatedAt: Date.now(),
        spawnedBy: orchestratorKey,
      },
      [siblingKey]: {
        sessionId: "sibling-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
      },
    });

    addSubagentRunForTests({
      runId: "run-worker",
      childSessionKey: workerKey,
      requesterSessionKey: orchestratorKey,
      requesterDisplayKey: orchestratorKey,
      task: "worker child",
      cleanup: "keep",
      createdAt: Date.now() - 30_000,
      startedAt: Date.now() - 30_000,
    });
    addSubagentRunForTests({
      runId: "run-sibling",
      childSessionKey: siblingKey,
      requesterSessionKey: "agent:main:main",
      requesterDisplayKey: "main",
      task: "sibling of orchestrator",
      cleanup: "keep",
      createdAt: Date.now() - 20_000,
      startedAt: Date.now() - 20_000,
    });

    const tool = createSubagentsTool({ agentSessionKey: orchestratorKey });
    const result = await tool.execute("call-orchestrator-list", { action: "list" });
    const details = result.details as {
      status?: string;
      requesterSessionKey?: string;
      total?: number;
      active?: Array<{ sessionKey?: string }>;
    };

    expect(details.status).toBe("ok");
    expect(details.requesterSessionKey).toBe(orchestratorKey);
    expect(details.total).toBe(1);
    expect(details.active).toEqual([
      expect.objectContaining({
        sessionKey: workerKey,
      }),
    ]);
  });

  it("leaf subagents cannot kill even explicitly-owned child sessions", async () => {
    const leafKey = "agent:main:subagent:leaf";
    const childKey = `${leafKey}:subagent:child`;

    writeStore(storePath, {
      [leafKey]: {
        sessionId: "leaf-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
        subagentRole: "leaf",
        subagentControlScope: "none",
      },
      [childKey]: {
        sessionId: "child-session",
        updatedAt: Date.now(),
        spawnedBy: leafKey,
        subagentRole: "leaf",
        subagentControlScope: "none",
      },
    });

    addSubagentRunForTests({
      runId: "run-child",
      childSessionKey: childKey,
      controllerSessionKey: leafKey,
      requesterSessionKey: leafKey,
      requesterDisplayKey: leafKey,
      task: "impossible child",
      cleanup: "keep",
      createdAt: Date.now() - 30_000,
      startedAt: Date.now() - 30_000,
    });

    const tool = createSubagentsTool({ agentSessionKey: leafKey });
    const result = await tool.execute("call-leaf-kill", {
      action: "kill",
      target: childKey,
    });

    expect(result.details).toMatchObject({
      status: "forbidden",
      error: "Leaf subagents cannot control other sessions.",
    });
    expect(callGatewayMock).not.toHaveBeenCalled();
  });

  it("leaf subagents cannot steer even explicitly-owned child sessions", async () => {
    const leafKey = "agent:main:subagent:leaf";
    const childKey = `${leafKey}:subagent:child`;

    writeStore(storePath, {
      [leafKey]: {
        sessionId: "leaf-session",
        updatedAt: Date.now(),
        spawnedBy: "agent:main:main",
        subagentRole: "leaf",
        subagentControlScope: "none",
      },
      [childKey]: {
        sessionId: "child-session",
        updatedAt: Date.now(),
        spawnedBy: leafKey,
        subagentRole: "leaf",
        subagentControlScope: "none",
      },
    });

    addSubagentRunForTests({
      runId: "run-child",
      childSessionKey: childKey,
      controllerSessionKey: leafKey,
      requesterSessionKey: leafKey,
      requesterDisplayKey: leafKey,
      task: "impossible child",
      cleanup: "keep",
      createdAt: Date.now() - 30_000,
      startedAt: Date.now() - 30_000,
    });

    const tool = createSubagentsTool({ agentSessionKey: leafKey });
    const result = await tool.execute("call-leaf-steer", {
      action: "steer",
      target: childKey,
      message: "continue",
    });

    expect(result.details).toMatchObject({
      status: "forbidden",
      error: "Leaf subagents cannot control other sessions.",
    });
    expect(callGatewayMock).not.toHaveBeenCalled();
  });
});
