import fs from "node:fs/promises";
import path from "node:path";
import {
  appendBootstrapPromptWarning,
  analyzeBootstrapBudget,
  buildBootstrapInjectionStats,
  buildBootstrapPromptWarning,
} from "../../../src/agents/bootstrap-budget.js";
import { resolveBootstrapContextForRun } from "../../../src/agents/bootstrap-files.js";
import { buildEmbeddedSystemPrompt } from "../../../src/agents/pi-embedded-runner/system-prompt.js";
import { buildAgentSystemPrompt } from "../../../src/agents/system-prompt.js";
import { createStubTool } from "../../../src/agents/test-helpers/pi-tool-stubs.js";
import { buildGroupChatContext, buildGroupIntro } from "../../../src/auto-reply/reply/groups.js";
import {
  buildInboundMetaSystemPrompt,
  buildInboundUserContextPrefix,
} from "../../../src/auto-reply/reply/inbound-meta.js";
import type { TemplateContext } from "../../../src/auto-reply/templating.js";
import { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
import type { OpenClawConfig } from "../../../src/config/config.js";
import { makeTempWorkspace, writeWorkspaceFile } from "../../../src/test-helpers/workspace.js";

export type PromptScenarioTurn = {
  id: string;
  label: string;
  systemPrompt: string;
  bodyPrompt: string;
  notes: string[];
};

export type PromptScenario = {
  scenario: string;
  focus: string;
  expectedStableSystemAfterTurnIds: string[];
  turns: PromptScenarioTurn[];
};

function buildCommonSystemParams(workspaceDir: string) {
  const toolNames = [
    "bash",
    "read",
    "edit",
    "grep",
    "glob",
    "message",
    "memory_search",
    "memory_get",
    "web_search",
    "x_search",
    "web_fetch",
  ];
  return {
    runtimeInfo: {
      agentId: "main",
      host: "cache-lab",
      repoRoot: workspaceDir,
      os: "Darwin 24.0.0",
      arch: "arm64",
      node: process.version,
      model: "anthropic/claude-sonnet-4-5",
      defaultModel: "anthropic/claude-sonnet-4-5",
      shell: "zsh",
    },
    userTimezone: "America/Los_Angeles",
    userTime: "Monday, March 16th, 2026 - 9:00 PM",
    userTimeFormat: "12" as const,
    toolNames,
  };
}

function buildSystemPrompt(params: {
  workspaceDir: string;
  extraSystemPrompt?: string;
  skillsPrompt?: string;
  reactionGuidance?: { level: "minimal" | "extensive"; channel: string };
  contextFiles?: Array<{ path: string; content: string }>;
}) {
  const { runtimeInfo, userTimezone, userTime, userTimeFormat, toolNames } =
    buildCommonSystemParams(params.workspaceDir);
  return buildAgentSystemPrompt({
    workspaceDir: params.workspaceDir,
    extraSystemPrompt: params.extraSystemPrompt,
    runtimeInfo,
    userTimezone,
    userTime,
    userTimeFormat,
    toolNames,
    modelAliasLines: [],
    promptMode: "full",
    acpEnabled: true,
    skillsPrompt: params.skillsPrompt,
    reactionGuidance: params.reactionGuidance,
    contextFiles: params.contextFiles,
  });
}

function buildAutoReplyBody(params: { ctx: TemplateContext; body: string; eventLine?: string }) {
  return [params.eventLine, buildInboundUserContextPrefix(params.ctx), params.body]
    .filter(Boolean)
    .join("\n\n");
}

async function readContextFiles(workspaceDir: string, fileNames: string[]) {
  return Promise.all(
    fileNames.map(async (fileName) => ({
      path: fileName,
      content: await fs.readFile(path.join(workspaceDir, fileName), "utf-8"),
    })),
  );
}

function buildAutoReplySystemPrompt(params: {
  workspaceDir: string;
  sessionCtx: TemplateContext;
  includeGroupChatContext?: boolean;
  includeGroupIntro?: boolean;
  groupSystemPrompt?: string;
}) {
  const extraSystemPromptParts = [
    buildInboundMetaSystemPrompt(params.sessionCtx),
    params.includeGroupChatContext ? buildGroupChatContext({ sessionCtx: params.sessionCtx }) : "",
    params.includeGroupIntro
      ? buildGroupIntro({
          cfg: {} as OpenClawConfig,
          sessionCtx: params.sessionCtx,
          defaultActivation: "mention",
          silentToken: SILENT_REPLY_TOKEN,
        })
      : "",
    params.groupSystemPrompt?.trim() ?? "",
  ].filter(Boolean);
  return buildSystemPrompt({
    workspaceDir: params.workspaceDir,
    extraSystemPrompt: extraSystemPromptParts.join("\n\n") || undefined,
  });
}

function buildToolRichSystemPrompt(params: {
  workspaceDir: string;
  skillsPrompt: string;
  contextFiles: Array<{ path: string; content: string }>;
}) {
  const { runtimeInfo, userTimezone, userTime, userTimeFormat } = buildCommonSystemParams(
    params.workspaceDir,
  );
  const tools = [
    "bash",
    "read",
    "edit",
    "grep",
    "glob",
    "message",
    "memory_search",
    "memory_get",
    "web_search",
    "x_search",
    "web_fetch",
  ].map((name) => ({ ...createStubTool(name), description: `${name} tool` }));
  return buildEmbeddedSystemPrompt({
    workspaceDir: params.workspaceDir,
    reasoningTagHint: false,
    runtimeInfo,
    tools,
    modelAliasLines: [],
    userTimezone,
    userTime,
    userTimeFormat,
    acpEnabled: true,
    skillsPrompt: params.skillsPrompt,
    reactionGuidance: { level: "extensive", channel: "Telegram" },
    contextFiles: params.contextFiles,
  });
}

function createDirectScenario(workspaceDir: string): PromptScenario {
  const baseCtx: TemplateContext = {
    Provider: "slack",
    Surface: "slack",
    OriginatingChannel: "slack",
    OriginatingTo: "D123",
    AccountId: "A1",
    ChatType: "direct",
    SenderId: "U1",
    SenderName: "Alice",
    Body: "hi",
    BodyStripped: "hi",
  };
  return {
    scenario: "auto-reply-direct",
    focus:
      "Normal direct-chat turns with ids, reply context, think hint, and runtime event body injection",
    expectedStableSystemAfterTurnIds: ["t2", "t3", "t4"],
    turns: [
      {
        id: "t1",
        label: "Direct turn with reply context",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "m1",
            ReplyToId: "r1",
            ReplyToBody: "prior message",
            WasMentioned: true,
          },
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "m1",
            ReplyToId: "r1",
            ReplyToBody: "prior message",
            WasMentioned: true,
          },
          body: "Please summarize yesterday's decision.",
        }),
        notes: ["Direct chat baseline", "Per-message ids and reply context change in body only"],
      },
      {
        id: "t2",
        label: "Direct turn with new message id",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "m2",
            ReplyToId: "r2",
          },
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "m2",
            ReplyToId: "r2",
          },
          body: "Now open the read tool and inspect AGENTS.md.",
        }),
        notes: ["Steady-state direct turn", "No runtime event"],
      },
      {
        id: "t3",
        label: "Direct turn with runtime event and think hint",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "m3",
            ReplyToId: "r3",
          },
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "m3",
            ReplyToId: "r3",
          },
          eventLine: "System: [t] Model switched.",
          body: "low use tools if needed and tell me which file controls startup behavior",
        }),
        notes: ["Touches runtime event body path", "Touches think-hint parsing path"],
      },
      {
        id: "t4",
        label: "Direct turn after runtime event",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "m4",
            ReplyToId: "r4",
          },
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "m4",
            ReplyToId: "r4",
          },
          body: "Repeat the startup file path only.",
        }),
        notes: ["Checks steady-state after event turn"],
      },
    ],
  };
}

function createGroupScenario(workspaceDir: string): PromptScenario {
  const baseCtx: TemplateContext = {
    Provider: "slack",
    Surface: "slack",
    OriginatingChannel: "slack",
    OriginatingTo: "C123",
    AccountId: "A1",
    ChatType: "group",
    GroupSubject: "ops",
    GroupChannel: "#ops",
    GroupMembers: "Bob, Cara, Dan, Eve",
    SenderId: "U2",
    SenderName: "Bob",
    Body: "hi",
    BodyStripped: "hi",
  };
  return {
    scenario: "auto-reply-group",
    focus: "Group chat bootstrap, steady state, and runtime event turns",
    expectedStableSystemAfterTurnIds: ["t3"],
    turns: [
      {
        id: "t1",
        label: "First group turn with one-time intro",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "g1",
            WasMentioned: true,
            InboundHistory: [{ sender: "Cara", timestamp: 1, body: "status?" }],
          },
          includeGroupChatContext: true,
          includeGroupIntro: true,
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "g1",
            WasMentioned: true,
            InboundHistory: [{ sender: "Cara", timestamp: 1, body: "status?" }],
          },
          body: "Can you investigate this issue?",
        }),
        notes: ["Expected first-turn bootstrap churn", "Not steady-state"],
      },
      {
        id: "t2",
        label: "Steady-state group turn",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "g2",
            WasMentioned: false,
            InboundHistory: [
              { sender: "Cara", timestamp: 1, body: "status?" },
              { sender: "Dan", timestamp: 2, body: "please help" },
            ],
          },
          includeGroupChatContext: true,
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "g2",
            WasMentioned: false,
            InboundHistory: [
              { sender: "Cara", timestamp: 1, body: "status?" },
              { sender: "Dan", timestamp: 2, body: "please help" },
            ],
          },
          body: "Give a short update.",
        }),
        notes: ["One-time intro gone", "Should settle afterward"],
      },
      {
        id: "t3",
        label: "Group turn with runtime event",
        systemPrompt: buildAutoReplySystemPrompt({
          workspaceDir,
          sessionCtx: {
            ...baseCtx,
            MessageSid: "g2",
            WasMentioned: false,
            InboundHistory: [
              { sender: "Cara", timestamp: 1, body: "status?" },
              { sender: "Dan", timestamp: 2, body: "please help" },
            ],
          },
          includeGroupChatContext: true,
        }),
        bodyPrompt: buildAutoReplyBody({
          ctx: {
            ...baseCtx,
            MessageSid: "g3",
            WasMentioned: true,
            InboundHistory: [
              { sender: "Cara", timestamp: 1, body: "status?" },
              { sender: "Dan", timestamp: 2, body: "please help" },
              { sender: "Eve", timestamp: 3, body: "what changed?" },
            ],
          },
          eventLine: "System: [t] Node connected.",
          body: "Tell the room whether tools are available.",
        }),
        notes: ["Runtime event lands in body", "System prompt should stay stable vs t2"],
      },
    ],
  };
}

async function createToolRichScenario(workspaceDir: string): Promise<PromptScenario> {
  const skillsPrompt = [
    "<available_skills>",
    "<skill><name>checks</name><description>Run checks before landing changes.</description><location>/skills/checks/SKILL.md</location></skill>",
    "<skill><name>release</name><description>Release OpenClaw safely.</description><location>/skills/release/SKILL.md</location></skill>",
    "</available_skills>",
  ].join("\n");
  const contextFiles = await readContextFiles(workspaceDir, ["AGENTS.md", "TOOLS.md", "SOUL.md"]);
  const systemPrompt = buildToolRichSystemPrompt({
    workspaceDir,
    skillsPrompt,
    contextFiles,
  });
  return {
    scenario: "tool-rich-agent-run",
    focus:
      "Tool-enabled system prompt with skills, reactions, workspace bootstrap, and a follow-up after fictional tool calls",
    expectedStableSystemAfterTurnIds: ["t2"],
    turns: [
      {
        id: "t1",
        label: "Tool-rich turn asking for search, read, and file edits",
        systemPrompt,
        bodyPrompt: [
          "Conversation info (untrusted metadata):",
          "```json",
          JSON.stringify({ message_id: "tool-1", sender_id: "U9", was_mentioned: true }, null, 2),
          "```",
          "",
          "high Search the workspace, read AGENTS.md, inspect the failing test, and propose a patch.",
        ].join("\n"),
        notes: ["Touches tool list in system prompt", "Touches high-thinking hint in body"],
      },
      {
        id: "t2",
        label: "Follow-up after a fictional tool call",
        systemPrompt,
        bodyPrompt: [
          "Conversation info (untrusted metadata):",
          "```json",
          JSON.stringify({ message_id: "tool-2", sender_id: "U9" }, null, 2),
          "```",
          "",
          "Tool transcript summary (untrusted, for context):",
          "```json",
          JSON.stringify(
            [
              { role: "assistant", action: "tool_use", name: "read", target: "AGENTS.md" },
              { role: "tool", name: "read", result: "Loaded AGENTS.md" },
              { role: "assistant", action: "tool_use", name: "grep", target: "failing test" },
              { role: "tool", name: "grep", result: "Matched src/foo.ts:42" },
            ],
            null,
            2,
          ),
          "```",
          "",
          "Continue and explain the root cause.",
        ].join("\n"),
        notes: ["Simulates tool-call-heavy conversation", "System prompt should stay stable"],
      },
    ],
  };
}

async function createBootstrapWarningScenario(workspaceDir: string): Promise<PromptScenario> {
  const bootstrapConfig = {
    agents: {
      defaults: {
        bootstrapMaxChars: 1_500,
        bootstrapTotalMaxChars: 2_200,
      },
    },
  } satisfies OpenClawConfig;
  const largeAgents = "# AGENTS.md\n\n" + "Rules.\n".repeat(5_000);
  const largeTools = "# TOOLS.md\n\n" + "Notes.\n".repeat(3_000);
  await writeWorkspaceFile({ dir: workspaceDir, name: "AGENTS.md", content: largeAgents });
  await writeWorkspaceFile({ dir: workspaceDir, name: "TOOLS.md", content: largeTools });
  const { bootstrapFiles, contextFiles } = await resolveBootstrapContextForRun({
    workspaceDir,
    config: bootstrapConfig,
  });
  const analysis = analyzeBootstrapBudget({
    files: buildBootstrapInjectionStats({
      bootstrapFiles,
      injectedFiles: contextFiles,
    }),
    bootstrapMaxChars: bootstrapConfig.agents.defaults.bootstrapMaxChars,
    bootstrapTotalMaxChars: bootstrapConfig.agents.defaults.bootstrapTotalMaxChars,
  });
  if (!analysis.hasTruncation) {
    throw new Error("bootstrap-warning scenario expected truncated bootstrap context");
  }
  const warningFirst = buildBootstrapPromptWarning({
    analysis,
    mode: "once",
    seenSignatures: [],
  });
  const warningSeen = buildBootstrapPromptWarning({
    analysis,
    mode: "once",
    seenSignatures: warningFirst.warningSignaturesSeen,
    previousSignature: warningFirst.signature,
  });
  const warningAlways = buildBootstrapPromptWarning({
    analysis,
    mode: "always",
    seenSignatures: warningFirst.warningSignaturesSeen,
    previousSignature: warningFirst.signature,
  });
  return {
    scenario: "bootstrap-warning",
    focus: "Workspace bootstrap truncation warnings inside # Project Context",
    expectedStableSystemAfterTurnIds: ["t2", "t3"],
    turns: [
      {
        id: "t1",
        label: "First warning emission",
        systemPrompt: buildSystemPrompt({
          workspaceDir,
          contextFiles,
        }),
        bodyPrompt: appendBootstrapPromptWarning("hello", warningFirst.lines),
        notes: ["Warning is appended to the turn body", "System prompt should stay stable"],
      },
      {
        id: "t2",
        label: "Same truncation signature after once-mode dedupe",
        systemPrompt: buildSystemPrompt({
          workspaceDir,
          contextFiles,
        }),
        bodyPrompt: appendBootstrapPromptWarning("hello again", warningSeen.lines),
        notes: ["Once-mode removes warning lines", "Only the body tail changes now"],
      },
      {
        id: "t3",
        label: "Always-mode warning",
        systemPrompt: buildSystemPrompt({
          workspaceDir,
          contextFiles,
        }),
        bodyPrompt: appendBootstrapPromptWarning("one more turn", warningAlways.lines),
        notes: [
          "Always-mode keeps warning in the body prompt tail",
          "System prompt remains stable",
        ],
      },
    ],
  };
}

async function createMaintenanceScenario(workspaceDir: string): Promise<PromptScenario> {
  await writeWorkspaceFile({
    dir: workspaceDir,
    name: "AGENTS.md",
    content: [
      "## Session Startup",
      "Read AGENTS.md and MEMORY.md before responding.",
      "",
      "## Red Lines",
      "Do not delete production data.",
      "",
      "## Safety",
      "Never reveal secrets.",
    ].join("\n"),
  });
  const memoryFlushPrompt = [
    "Pre-compaction memory flush.",
    "Store durable memories only in memory/2026-03-15.md (create memory/ if needed).",
    "Treat workspace bootstrap/reference files such as MEMORY.md, SOUL.md, TOOLS.md, and AGENTS.md as read-only during this flush; never overwrite, replace, or edit them.",
    "If nothing to store, reply with NO_REPLY.",
    "Current time: Sunday, March 15th, 2026 - 9:30 PM (America/Los_Angeles) / 2026-03-16 04:30 UTC",
  ].join("\n");
  const memoryFlushSystemPrompt = buildSystemPrompt({
    workspaceDir,
    extraSystemPrompt: [
      "Pre-compaction memory flush turn.",
      "The session is near auto-compaction; capture durable memories to disk.",
      "Store durable memories only in memory/YYYY-MM-DD.md (create memory/ if needed).",
      "You may reply, but usually NO_REPLY is correct.",
    ].join(" "),
  });
  const postCompaction = [
    "[Post-compaction context refresh]",
    "",
    "Session was just compacted. The conversation summary above is a hint, NOT a substitute for your startup sequence.",
    "",
    "Critical rules from AGENTS.md:",
    "",
    "## Session Startup",
    "Read AGENTS.md and MEMORY.md before responding.",
    "",
    "## Red Lines",
    "Do not delete production data.",
    "",
    "Current time: Sunday, March 15th, 2026 - 9:30 PM (America/Los_Angeles) / 2026-03-16 04:30 UTC",
  ].join("\n");
  const postCompactionSystemPrompt = buildSystemPrompt({
    workspaceDir,
    extraSystemPrompt: buildInboundMetaSystemPrompt({
      Provider: "slack",
      Surface: "slack",
      OriginatingChannel: "slack",
      OriginatingTo: "D123",
      AccountId: "A1",
      ChatType: "direct",
    }),
  });
  return {
    scenario: "maintenance-prompts",
    focus: "Memory flush and post-compaction maintenance prompts",
    expectedStableSystemAfterTurnIds: [],
    turns: [
      {
        id: "t1",
        label: "Pre-compaction memory flush run",
        systemPrompt: memoryFlushSystemPrompt,
        bodyPrompt: memoryFlushPrompt,
        notes: [
          "Writes to memory/2026-03-15.md",
          "Separate maintenance run; expected to differ from normal user turns",
        ],
      },
      {
        id: "t2",
        label: "Post-compaction refresh context run",
        systemPrompt: postCompactionSystemPrompt,
        bodyPrompt: postCompaction,
        notes: [
          "Separate maintenance context payload",
          "Expected to differ from normal user turns",
        ],
      },
    ],
  };
}

export async function createWorkspaceWithPromptCompositionFiles(): Promise<string> {
  const workspaceDir = await makeTempWorkspace("openclaw-prompt-cache-");
  await writeWorkspaceFile({
    dir: workspaceDir,
    name: "AGENTS.md",
    content: [
      "# AGENTS.md",
      "",
      "## Session Startup",
      "Read AGENTS.md and TOOLS.md before making changes.",
      "",
      "## Red Lines",
      "Do not rewrite user commits.",
    ].join("\n"),
  });
  await writeWorkspaceFile({
    dir: workspaceDir,
    name: "TOOLS.md",
    content: "# TOOLS.md\n\nUse rg before grep.\n",
  });
  await writeWorkspaceFile({
    dir: workspaceDir,
    name: "SOUL.md",
    content: "# SOUL.md\n\nBe concise but kind.\n",
  });
  return workspaceDir;
}

export async function createPromptCompositionScenarios(): Promise<{
  workspaceDir: string;
  warningWorkspaceDir: string;
  scenarios: PromptScenario[];
  cleanup: () => Promise<void>;
}> {
  const workspaceDir = await createWorkspaceWithPromptCompositionFiles();
  const warningWorkspaceDir = await makeTempWorkspace("openclaw-prompt-cache-warning-");
  const scenarios = [
    createDirectScenario(workspaceDir),
    createGroupScenario(workspaceDir),
    await createToolRichScenario(workspaceDir),
    await createBootstrapWarningScenario(warningWorkspaceDir),
    await createMaintenanceScenario(workspaceDir),
  ];
  return {
    workspaceDir,
    warningWorkspaceDir,
    scenarios,
    cleanup: async () => {
      await fs.rm(workspaceDir, { recursive: true, force: true });
      await fs.rm(warningWorkspaceDir, { recursive: true, force: true });
    },
  };
}
