import assert from "node:assert/strict";
import { test } from "bun:test";

import {
  classifyWorkbenchEmptySessionGcCandidate,
  runWorkbenchEmptySessionGc
} from "./workbench-empty-session-gc.ts";

test("workbench empty session GC archives only stale empty idle sessions", async () => {
  const sessions = new Map([
    ["ses_empty_old", session({ id: "ses_empty_old", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z" })],
    ["ses_empty_fresh", session({ id: "ses_empty_fresh", status: "idle", updatedAt: "2026-06-18T00:15:30.000Z" })],
    ["ses_with_user", session({ id: "ses_with_user", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z", messages: [{ role: "user", text: "keep" }] })],
    ["ses_running", session({ id: "ses_running", status: "running", updatedAt: "2026-06-18T00:00:00.000Z" })],
    ["ses_trace", session({ id: "ses_trace", status: "idle", updatedAt: "2026-06-18T00:00:00.000Z", lastTraceId: "trc_keep" })],
    ["ses_completed", session({ id: "ses_completed", status: "completed", updatedAt: "2026-06-18T00:00:00.000Z" })]
  ]);
  const store = {
    async listAgentSessionsForUser(input = {}) {
      assert.equal(input.ownerScoped, false);
      assert.equal(input.actorRole, "admin");
      return [...sessions.values()].filter((item) => item.status !== "archived");
    },
    async archiveAgentConversation(input = {}) {
      assert.equal(input.ownerScoped, false);
      assert.equal(input.actorRole, "admin");
      const current = sessions.get(input.sessionId);
      if (!current || current.status === "archived") return { count: 0 };
      sessions.set(input.sessionId, { ...current, status: "archived", endedAt: input.now, updatedAt: input.now });
      return { count: 1 };
    }
  };

  const result = await runWorkbenchEmptySessionGc({
    store,
    ttlMs: 10 * 60 * 1000,
    now: "2026-06-18T00:20:00.000Z"
  });

  assert.equal(result.ok, true);
  assert.deepEqual(result.archivedSessionIds, ["ses_empty_old"]);
  assert.equal(sessions.get("ses_empty_old")?.status, "archived");
  assert.equal(sessions.get("ses_empty_fresh")?.status, "idle");
  assert.equal(sessions.get("ses_with_user")?.status, "idle");
  assert.equal(sessions.get("ses_running")?.status, "running");
  assert.equal(sessions.get("ses_trace")?.status, "idle");
  assert.equal(sessions.get("ses_completed")?.status, "completed");
  assert.equal(result.keptByReason.ttl_not_elapsed, 1);
  assert.equal(result.keptByReason.has_messages, 1);
  assert.equal(result.keptByReason.running_or_admitting, 1);
  assert.equal(result.keptByReason.has_trace_or_terminal_evidence, 1);
  assert.equal(result.keptByReason.terminal_result_status, 1);
});

test("workbench empty session GC classifier uses canonical activity timestamp", () => {
  const candidate = session({
    id: "ses_touched",
    status: "idle",
    updatedAt: "2026-06-18T00:00:00.000Z",
    session: { createdAt: "2026-06-18T00:00:00.000Z", lastTouchedAt: "2026-06-18T00:19:00.000Z", messageCount: 0 }
  });

  assert.deepEqual(
    classifyWorkbenchEmptySessionGcCandidate(candidate, { nowMs: Date.parse("2026-06-18T00:20:00.000Z"), ttlMs: 10 * 60 * 1000 }),
    { eligible: false, reason: "ttl_not_elapsed" }
  );
});

function session(input: any) {
  const messages = input.messages ?? [];
  return {
    id: input.id,
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: input.status,
    startedAt: input.startedAt ?? input.updatedAt,
    endedAt: null,
    ownerUserId: "usr_gc",
    conversationId: `cnv_${input.id.slice("ses_".length)}`,
    threadId: null,
    lastTraceId: input.lastTraceId ?? null,
    updatedAt: input.updatedAt,
    session: {
      source: "test",
      createdAt: input.startedAt ?? input.updatedAt,
      updatedAt: input.updatedAt,
      messageCount: messages.length,
      messages,
      ...(input.session ?? {}),
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
}
