import { afterEach, describe, expect, test, vi } from "vitest";
import { SUBAGENT_ENV_HINT_KEYS } from "#src/authority/permission-forwarding";
import {
  isRegisteredSubagentChild,
  isSubagentExecutionContext,
  normalizeFilesystemPath,
  type SubagentDetectionContext,
} from "#src/authority/subagent-context";
import { SubagentSessionRegistry } from "#src/authority/subagent-registry";
import { posixPathFlavor, win32PathFlavor } from "#src/path/path-flavor";

afterEach(() => {
  vi.unstubAllEnvs();
  vi.restoreAllMocks();
});

function makeCtx(
  sessionDir: string | null,
  sessionId: string = "",
): SubagentDetectionContext {
  return {
    sessionManager: {
      getSessionDir: vi.fn(() => sessionDir ?? ""),
      getSessionId: vi.fn(() => sessionId),
    },
  };
}

describe("isRegisteredSubagentChild", () => {
  const childSessionId = "child-session-abc";

  test("returns true when the session id is registered", () => {
    const registry = new SubagentSessionRegistry();
    registry.register(childSessionId, {});
    expect(
      isRegisteredSubagentChild(makeCtx(null, childSessionId), registry),
    ).toBe(true);
  });

  test("returns false when the session id is not registered", () => {
    const registry = new SubagentSessionRegistry();
    expect(
      isRegisteredSubagentChild(makeCtx(null, childSessionId), registry),
    ).toBe(false);
  });

  test("returns false when the session id is empty", () => {
    const registry = new SubagentSessionRegistry();
    registry.register("", {});
    expect(isRegisteredSubagentChild(makeCtx(null, ""), registry)).toBe(false);
  });

  test("returns false when getSessionId throws", () => {
    const registry = new SubagentSessionRegistry();
    registry.register(childSessionId, {});
    const ctx: SubagentDetectionContext = {
      sessionManager: {
        getSessionDir: vi.fn(() => ""),
        getSessionId: vi.fn(() => {
          throw new Error("session id unavailable");
        }),
      },
    };
    expect(isRegisteredSubagentChild(ctx, registry)).toBe(false);
  });
});

describe("normalizeFilesystemPath", () => {
  test("normalizes a simple absolute path", () => {
    expect(normalizeFilesystemPath("/projects/my-app", posixPathFlavor)).toBe(
      "/projects/my-app",
    );
  });

  test("collapses redundant separators", () => {
    expect(normalizeFilesystemPath("/projects//my-app", posixPathFlavor)).toBe(
      "/projects/my-app",
    );
  });

  test("resolves . and .. segments", () => {
    expect(
      normalizeFilesystemPath("/projects/my-app/../other", posixPathFlavor),
    ).toBe("/projects/other");
  });

  test("win32: lowercases and normalizes with win32 separators", () => {
    expect(
      normalizeFilesystemPath("C:\\Projects\\My-App", win32PathFlavor),
    ).toBe("c:\\projects\\my-app");
  });

  test("posix: leaves case untouched", () => {
    expect(normalizeFilesystemPath("/Projects/My-App", posixPathFlavor)).toBe(
      "/Projects/My-App",
    );
  });
});

describe("isSubagentExecutionContext — injected platform (#510)", () => {
  test("win32: detects a subagent session dir case-insensitively", () => {
    const subagentRoot = "C:\\Sessions\\Subagents";
    const sessionDir = "c:\\sessions\\subagents\\child";
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(true);
  });

  test("posix: the same mixed-case dir is not a subagent context", () => {
    const subagentRoot = "/Sessions/Subagents";
    const sessionDir = "/sessions/subagents/child";
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(false);
  });
});

describe("isSubagentExecutionContext — env hint detection", () => {
  test("returns true when PI_IS_SUBAGENT is set", () => {
    vi.stubEnv("PI_IS_SUBAGENT", "true");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_SESSION_ID is set", () => {
    vi.stubEnv("PI_SUBAGENT_SESSION_ID", "abc123");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_AGENT_ROUTER_SUBAGENT is set", () => {
    vi.stubEnv("PI_AGENT_ROUTER_SUBAGENT", "1");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  // nicobailon/pi-subagents keys
  test("returns true when PI_SUBAGENT_CHILD is set", () => {
    vi.stubEnv("PI_SUBAGENT_CHILD", "1");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_RUN_ID is set", () => {
    vi.stubEnv("PI_SUBAGENT_RUN_ID", "run-abc");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_CHILD_AGENT is set", () => {
    vi.stubEnv("PI_SUBAGENT_CHILD_AGENT", "worker");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_DEPTH is set", () => {
    vi.stubEnv("PI_SUBAGENT_DEPTH", "1");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_DEPTH is zero (depth-0 is still a subagent context)", () => {
    vi.stubEnv("PI_SUBAGENT_DEPTH", "0");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  // HazAT/pi-interactive-subagents keys
  test("returns true when PI_SUBAGENT_NAME is set", () => {
    vi.stubEnv("PI_SUBAGENT_NAME", "my-agent");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_ID is set", () => {
    vi.stubEnv("PI_SUBAGENT_ID", "id-xyz");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_SESSION is set", () => {
    vi.stubEnv("PI_SUBAGENT_SESSION", "session-xyz");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when PI_SUBAGENT_ACTIVITY_FILE is set", () => {
    vi.stubEnv("PI_SUBAGENT_ACTIVITY_FILE", "/tmp/activity.json");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("covers all declared SUBAGENT_ENV_HINT_KEYS", () => {
    // Verify the keys we test match what the module declares.
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_IS_SUBAGENT");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_SESSION_ID");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_AGENT_ROUTER_SUBAGENT");
    // nicobailon/pi-subagents
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_CHILD");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_RUN_ID");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_CHILD_AGENT");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_DEPTH");
    // HazAT/pi-interactive-subagents
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_NAME");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_ID");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_SESSION");
    expect(SUBAGENT_ENV_HINT_KEYS).toContain("PI_SUBAGENT_ACTIVITY_FILE");
  });

  test("returns false when env hint value is empty string", () => {
    vi.stubEnv("PI_IS_SUBAGENT", "");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when env hint value is whitespace only", () => {
    vi.stubEnv("PI_IS_SUBAGENT", "   ");
    expect(
      isSubagentExecutionContext(
        makeCtx(null),
        "/sessions/subagents",
        posixPathFlavor,
      ),
    ).toBe(false);
  });
});

describe("isSubagentExecutionContext — session dir detection", () => {
  const subagentRoot = "/home/user/.pi/agent/sessions/subagents";

  test("returns true when session dir is within subagent root", () => {
    const sessionDir = `${subagentRoot}/session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when session dir equals subagent root", () => {
    expect(
      isSubagentExecutionContext(
        makeCtx(subagentRoot),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns false when session dir is outside subagent root", () => {
    const sessionDir = "/home/user/.pi/agent/sessions/main-session";
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when session dir is a sibling with shared prefix", () => {
    // "/sessions/subagents-extra" should not match root "/sessions/subagents"
    const sessionDir = `${subagentRoot}-extra/session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when a `..` segment escapes the subagent root", () => {
    // Normalizes to /home/user/.pi/agent/sessions/evil/session-abc — outside.
    const sessionDir = `${subagentRoot}/../evil/session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(false);
  });

  test("returns true when a `..` segment resolves back inside the root", () => {
    // Normalizes to /home/user/.pi/agent/sessions/subagents/session-abc — inside.
    const sessionDir = `${subagentRoot}/nested/../session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(true);
  });

  test("returns false when session dir is under a different root", () => {
    const sessionDir = "/var/other/subagents/session-abc";
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        posixPathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when getSessionDir returns null", () => {
    expect(
      isSubagentExecutionContext(makeCtx(null), subagentRoot, posixPathFlavor),
    ).toBe(false);
  });

  test("returns false when getSessionDir returns empty string", () => {
    expect(
      isSubagentExecutionContext(makeCtx(""), subagentRoot, posixPathFlavor),
    ).toBe(false);
  });
});

describe("isSubagentExecutionContext — session dir detection (win32 flavor)", () => {
  const subagentRoot = "C:\\Users\\dev\\.pi\\agent\\sessions\\subagents";

  test("returns true when session dir is within subagent root", () => {
    const sessionDir = `${subagentRoot}\\session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(true);
  });

  test("returns true when session dir equals subagent root (case-insensitive)", () => {
    expect(
      isSubagentExecutionContext(
        makeCtx(subagentRoot.toUpperCase()),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(true);
  });

  test("returns false when session dir is a sibling with shared prefix", () => {
    const sessionDir = `${subagentRoot}-extra\\session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when a `..` segment escapes the subagent root", () => {
    const sessionDir = `${subagentRoot}\\..\\evil\\session-abc`;
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(false);
  });

  test("returns false when session dir is on a different drive", () => {
    const sessionDir =
      "D:\\Users\\dev\\.pi\\agent\\sessions\\subagents\\session-abc";
    expect(
      isSubagentExecutionContext(
        makeCtx(sessionDir),
        subagentRoot,
        win32PathFlavor,
      ),
    ).toBe(false);
  });
});

describe("isSubagentExecutionContext — registry detection", () => {
  const subagentRoot = "/home/user/.pi/agent/sessions/subagents";
  const outsideDir =
    "/home/user/projects/my-app/.pi/agent/sessions/parent/tasks";
  const childSessionId = "child-session-abc";

  test("returns true when session id is registered (no env vars, dir outside filesystem root)", () => {
    const registry = new SubagentSessionRegistry();
    registry.register(childSessionId, {});
    expect(
      isSubagentExecutionContext(
        makeCtx(outsideDir, childSessionId),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(true);
  });

  test("returns true when registered session has a parentSessionId", () => {
    const registry = new SubagentSessionRegistry();
    registry.register(childSessionId, { parentSessionId: "parent-123" });
    expect(
      isSubagentExecutionContext(
        makeCtx(outsideDir, childSessionId),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(true);
  });

  test("returns false when registry is provided but session id is not registered", () => {
    const registry = new SubagentSessionRegistry();
    expect(
      isSubagentExecutionContext(
        makeCtx(outsideDir, childSessionId),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(false);
  });

  test("returns false when session id is empty and registry has no matching entry", () => {
    const registry = new SubagentSessionRegistry();
    expect(
      isSubagentExecutionContext(
        makeCtx(null, ""),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(false);
  });

  test("registry check takes priority over env var detection", () => {
    // Registry says registered; env var not set — should still return true.
    const registry = new SubagentSessionRegistry();
    registry.register(childSessionId, {});
    // Confirm no env var is set
    expect(process.env.PI_IS_SUBAGENT).toBeUndefined();
    expect(
      isSubagentExecutionContext(
        makeCtx(outsideDir, childSessionId),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(true);
  });

  test("unregistered session falls through to env var detection", () => {
    vi.stubEnv("PI_IS_SUBAGENT", "true");
    const registry = new SubagentSessionRegistry(); // empty — childSessionId not registered
    // Env var present → still true even without registry entry
    expect(
      isSubagentExecutionContext(
        makeCtx(outsideDir, childSessionId),
        subagentRoot,
        posixPathFlavor,
        registry,
      ),
    ).toBe(true);
  });

  test("no registry passed — existing behaviour unchanged", () => {
    // Ensure the parameter is truly optional (no registry arg)
    expect(
      isSubagentExecutionContext(makeCtx(null), subagentRoot, posixPathFlavor),
    ).toBe(false);
  });
});
