// Session Log Mentions tests cover session log mentions script behavior.
import fs from "node:fs/promises";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { afterEach, describe, expect, it } from "vitest";
import {
  countSessionLogMentions,
  readSessionLogMentionLimits,
} from "../../scripts/e2e/lib/session-log-mentions.ts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";

const tempRoots = useAutoCleanupTempDirTracker(afterEach);

describe("session log mention scanner", () => {
  it("counts mentions across bounded session logs", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    await fs.writeFile(path.join(root, "one.jsonl"), "API.read MCP.fixture API.read\n");
    await fs.writeFile(path.join(root, "two.jsonl"), "MCP.fixture\n");
    await fs.writeFile(path.join(root, "ignored.txt"), "API.read\n");

    await expect(
      countSessionLogMentions({
        sessionsDir: root,
        needles: {
          apiFileRead: "API.read",
          mcpNamespace: "MCP.fixture",
        },
      }),
    ).resolves.toEqual({
      apiFileRead: 2,
      mcpNamespace: 2,
    });
  });

  it("does not count user prompt lines as runtime mention proof", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    await fs.writeFile(
      path.join(root, "prompts.jsonl"),
      [
        JSON.stringify({
          role: "user",
          content: 'Use API.read("mcp/index.d.ts") and MCP.fixture.lookupNote.',
        }),
        JSON.stringify({
          message: {
            role: "user",
            content: "Call fixture__lookup_note.",
          },
        }),
        JSON.stringify({
          role: "assistant",
          content: 'API.read MCP.fixture fixture__lookup_note tools.search("lookup note")',
        }),
        "raw transcript fallback API.read",
        "",
      ].join("\n"),
    );

    await expect(
      countSessionLogMentions({
        sessionsDir: root,
        needles: {
          apiFileRead: "API.read",
          mcpNamespace: "MCP.fixture",
          mcpTool: "fixture__lookup_note",
          toolSearchPollution: 'tools.search("lookup note"',
        },
      }),
    ).resolves.toEqual({
      apiFileRead: 2,
      mcpNamespace: 1,
      mcpTool: 1,
      toolSearchPollution: 1,
    });
  });

  it("counts mentions from SQLite transcript rows", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    const sessionsDir = path.join(root, "agents", "main", "sessions");
    const sqlitePath = path.join(root, "agents", "main", "agent", "openclaw-agent.sqlite");
    await fs.mkdir(path.dirname(sqlitePath), { recursive: true });
    const db = new DatabaseSync(sqlitePath);
    try {
      db.exec(`
        CREATE TABLE transcript_events (
          session_id TEXT NOT NULL,
          seq INTEGER NOT NULL,
          event_json TEXT NOT NULL,
          created_at INTEGER NOT NULL,
          PRIMARY KEY (session_id, seq)
        );
      `);
      const insert = db.prepare(
        "INSERT INTO transcript_events (session_id, seq, event_json, created_at) VALUES (?, ?, ?, ?)",
      );
      insert.run(
        "sqlite-session",
        1,
        JSON.stringify({
          message: {
            role: "user",
            content: "Use API.read and MCP.fixture from the prompt.",
          },
        }),
        1,
      );
      insert.run(
        "sqlite-session",
        2,
        JSON.stringify({
          message: {
            role: "assistant",
            content: 'API.read MCP.fixture fixture__lookup_note tools.search("lookup note")',
          },
        }),
        2,
      );
    } finally {
      db.close();
    }

    await expect(
      countSessionLogMentions({
        sessionsDir,
        needles: {
          apiFileRead: "API.read",
          mcpNamespace: "MCP.fixture",
          mcpTool: "fixture__lookup_note",
          toolSearchPollution: 'tools.search("lookup note"',
        },
      }),
    ).resolves.toEqual({
      apiFileRead: 1,
      mcpNamespace: 1,
      mcpTool: 1,
      toolSearchPollution: 1,
    });
  });

  it("rejects oversized SQLite transcript rows before counting them", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    const sessionsDir = path.join(root, "agents", "main", "sessions");
    const sqlitePath = path.join(root, "agents", "main", "agent", "openclaw-agent.sqlite");
    await fs.mkdir(path.dirname(sqlitePath), { recursive: true });
    const db = new DatabaseSync(sqlitePath);
    try {
      db.exec(`
        CREATE TABLE transcript_events (
          session_id TEXT NOT NULL,
          seq INTEGER NOT NULL,
          event_json TEXT NOT NULL,
          created_at INTEGER NOT NULL,
          PRIMARY KEY (session_id, seq)
        );
      `);
      db.prepare(
        "INSERT INTO transcript_events (session_id, seq, event_json, created_at) VALUES (?, ?, ?, ?)",
      ).run("sqlite-session", 1, "API.read ".repeat(16), 1);
    } finally {
      db.close();
    }

    await expect(
      countSessionLogMentions({
        limits: { fileMaxBytes: 32, totalMaxBytes: 1024 },
        sessionsDir,
        needles: {
          apiFileRead: "API.read",
        },
      }),
    ).rejects.toMatchObject({
      code: "ETOOBIG",
      message: expect.stringContaining("per-file limit"),
    });
  });

  it("returns zero counts when the sessions directory is absent", async () => {
    await expect(
      countSessionLogMentions({
        sessionsDir: path.join(tempRoots.make("openclaw-session-log-mentions-"), "missing"),
        needles: {
          apiFileRead: "API.read",
        },
      }),
    ).resolves.toEqual({
      apiFileRead: 0,
    });
  });

  it("rejects oversized session log files before loading them", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    await fs.writeFile(path.join(root, "huge.jsonl"), "x".repeat(64));

    await expect(
      countSessionLogMentions({
        limits: { fileMaxBytes: 32, totalMaxBytes: 1024 },
        sessionsDir: root,
        needles: {
          apiFileRead: "API.read",
        },
      }),
    ).rejects.toMatchObject({
      code: "ETOOBIG",
      message: expect.stringContaining("per-file limit"),
    });
  });

  it("rejects aggregate session log scans that exceed the total ceiling", async () => {
    const root = tempRoots.make("openclaw-session-log-mentions-");
    await fs.writeFile(path.join(root, "one.jsonl"), "x".repeat(24));
    await fs.writeFile(path.join(root, "two.jsonl"), "x".repeat(24));

    await expect(
      countSessionLogMentions({
        limits: { fileMaxBytes: 64, totalMaxBytes: 32 },
        sessionsDir: root,
        needles: {
          apiFileRead: "API.read",
        },
      }),
    ).rejects.toMatchObject({
      code: "ETOOBIG",
      message: expect.stringContaining("total limit"),
    });
  });

  it("rejects loose numeric env limits instead of parsing prefixes", () => {
    expect(() =>
      readSessionLogMentionLimits({
        OPENCLAW_SESSION_LOG_MENTION_FILE_MAX_BYTES: "1e3",
      }),
    ).toThrow("invalid OPENCLAW_SESSION_LOG_MENTION_FILE_MAX_BYTES: 1e3");
    expect(() =>
      readSessionLogMentionLimits({
        OPENCLAW_SESSION_LOG_MENTION_TOTAL_MAX_BYTES: "1000ms",
      }),
    ).toThrow("invalid OPENCLAW_SESSION_LOG_MENTION_TOTAL_MAX_BYTES: 1000ms");
  });
});
