import { describe, expect, it, vi } from "vitest";
import {
  expectsSubagentFollowup,
  isLikelyInterimCronMessage,
  readDescendantSubagentFallbackReply,
} from "./subagent-followup.js";

vi.mock("../../agents/subagent-registry.js", () => ({
  countActiveDescendantRuns: vi.fn().mockReturnValue(0),
  listDescendantRunsForRequester: vi.fn().mockReturnValue([]),
}));

vi.mock("../../agents/tools/agent-step.js", () => ({
  readLatestAssistantReply: vi.fn().mockResolvedValue(undefined),
}));

const { listDescendantRunsForRequester } = await import("../../agents/subagent-registry.js");
const { readLatestAssistantReply } = await import("../../agents/tools/agent-step.js");

describe("isLikelyInterimCronMessage", () => {
  it("detects 'on it' as interim", () => {
    expect(isLikelyInterimCronMessage("on it")).toBe(true);
  });
  it("detects subagent-related interim text", () => {
    expect(isLikelyInterimCronMessage("spawned a subagent, it'll auto-announce when done")).toBe(
      true,
    );
  });
  it("rejects substantive content", () => {
    expect(isLikelyInterimCronMessage("Here are your results: revenue was $5000 this month")).toBe(
      false,
    );
  });
  it("treats empty as interim", () => {
    expect(isLikelyInterimCronMessage("")).toBe(true);
  });
});

describe("expectsSubagentFollowup", () => {
  it("returns true for subagent spawn hints", () => {
    expect(expectsSubagentFollowup("subagent spawned")).toBe(true);
    expect(expectsSubagentFollowup("spawned a subagent")).toBe(true);
    expect(expectsSubagentFollowup("it'll auto-announce when done")).toBe(true);
    expect(expectsSubagentFollowup("both subagents are running")).toBe(true);
  });
  it("returns false for plain interim text", () => {
    expect(expectsSubagentFollowup("on it")).toBe(false);
    expect(expectsSubagentFollowup("working on it")).toBe(false);
  });
  it("returns false for empty string", () => {
    expect(expectsSubagentFollowup("")).toBe(false);
  });
});

describe("readDescendantSubagentFallbackReply", () => {
  const runStartedAt = 1000;

  it("returns undefined when no descendants exist", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([]);
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBeUndefined();
  });

  it("reads reply from child session transcript", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 2000,
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue("child output text");
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBe("child output text");
  });

  it("falls back to frozenResultText when session transcript unavailable", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "delete",
        createdAt: 1000,
        endedAt: 2000,
        frozenResultText: "frozen child output",
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue(undefined);
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBe("frozen child output");
  });

  it("prefers session transcript over frozenResultText", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 2000,
        frozenResultText: "frozen text",
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue("live transcript text");
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBe("live transcript text");
  });

  it("joins replies from multiple descendants", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 2000,
        frozenResultText: "first child output",
      },
      {
        runId: "run-2",
        childSessionKey: "child-2",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-2",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 3000,
        frozenResultText: "second child output",
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue(undefined);
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBe("first child output\n\nsecond child output");
  });

  it("skips SILENT_REPLY_TOKEN descendants", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 2000,
      },
      {
        runId: "run-2",
        childSessionKey: "child-2",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-2",
        cleanup: "keep",
        createdAt: 1000,
        endedAt: 3000,
        frozenResultText: "useful output",
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockImplementation(async (params) => {
      if (params.sessionKey === "child-1") {
        return "NO_REPLY";
      }
      return undefined;
    });
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBe("useful output");
  });

  it("returns undefined when frozenResultText is null", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "delete",
        createdAt: 1000,
        endedAt: 2000,
        frozenResultText: null,
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue(undefined);
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBeUndefined();
  });

  it("ignores descendants that ended before run started", async () => {
    vi.mocked(listDescendantRunsForRequester).mockReturnValue([
      {
        runId: "run-1",
        childSessionKey: "child-1",
        requesterSessionKey: "test-session",
        requesterDisplayKey: "test-session",
        task: "task-1",
        cleanup: "keep",
        createdAt: 500,
        endedAt: 900,
        frozenResultText: "stale output from previous run",
      },
    ]);
    vi.mocked(readLatestAssistantReply).mockResolvedValue(undefined);
    const result = await readDescendantSubagentFallbackReply({
      sessionKey: "test-session",
      runStartedAt,
    });
    expect(result).toBeUndefined();
  });
});
