import { test } from "node:test";
import assert from "node:assert/strict";
import { DatabaseSync } from "node:sqlite";
import { buildMemoryAwareInstructions, recordAiToolExchange } from "./aiMemory.ts";
import { WorkMemoryStore } from "../memory/store.ts";

function db(): DatabaseSync {
  const d = new DatabaseSync(":memory:");
  d.exec(`
    CREATE TABLE contact (
      id TEXT PRIMARY KEY,
      workspace_id TEXT NOT NULL,
      display_name TEXT,
      updated_at INTEGER NOT NULL,
      deleted_at INTEGER
    );
    CREATE TABLE unified_conversation (
      id TEXT PRIMARY KEY,
      workspace_id TEXT NOT NULL,
      channel TEXT NOT NULL,
      contact_id TEXT,
      deleted_at INTEGER
    );
    CREATE TABLE contact_merge (
      id TEXT PRIMARY KEY,
      workspace_id TEXT NOT NULL,
      primary_contact_id TEXT NOT NULL,
      merged_contact_id TEXT NOT NULL,
      signal TEXT,
      conv_ids_json TEXT NOT NULL,
      created_at INTEGER NOT NULL,
      undone_at INTEGER
    );
  `);
  return d;
}

test("AI instructions include frozen business, owner, and resolved contact memory", () => {
  const d = db();
  d.prepare(`INSERT INTO contact(id, workspace_id, display_name, updated_at) VALUES ('contact-1', 'local', 'Ada', 1)`).run();
  d.prepare(`INSERT INTO unified_conversation(id, workspace_id, channel, contact_id) VALUES ('conv-1', 'local', 'LINKEDIN', 'contact-1')`).run();
  const store = new WorkMemoryStore(d);
  store.add("business", "Business memory alpha: use LocalDB only.");
  store.add("owner", "Owner memory beta: keep updates direct.");
  store.add("contact:contact-1", "Contact memory gamma: prefers Friday follow-up.");

  const built = buildMemoryAwareInstructions(d, {
    tool: "ai_draft_reply",
    conversationId: "conv-1",
    baseSystemInstructions: "Base draft instructions.",
  });

  assert.equal(built.resolved_contact_id, "contact-1");
  assert.deepEqual(built.memory_scopes, ["business", "owner", "contact:contact-1"]);
  assert.match(built.system_instructions, /Business memory alpha/);
  assert.match(built.system_instructions, /Owner memory beta/);
  assert.match(built.system_instructions, /Contact memory gamma/);
  assert.match(built.system_instructions, /Base draft instructions/);

  store.add("contact:contact-1", "Contact memory delta: added after build.");
  assert.doesNotMatch(built.system_instructions, /delta/);
});

test("AI contact scope is only included when the conversation resolves to a real contact", () => {
  const d = db();
  d.prepare(`INSERT INTO unified_conversation(id, workspace_id, channel, contact_id) VALUES ('conv-missing', 'local', 'EMAIL', 'missing-contact')`).run();
  const store = new WorkMemoryStore(d);
  store.add("business", "Business memory only.");
  store.add("contact:missing-contact", "This should not leak.");

  const built = buildMemoryAwareInstructions(d, {
    tool: "ai_summarize_thread",
    conversationId: "conv-missing",
    baseSystemInstructions: "Base summary instructions.",
  });

  assert.equal(built.resolved_contact_id, null);
  assert.deepEqual(built.memory_scopes, ["business", "owner"]);
  assert.match(built.system_instructions, /Business memory only/);
  assert.doesNotMatch(built.system_instructions, /should not leak/);
});

test("AI tool exchanges are recorded into agent sessions and searchable", () => {
  const d = db();
  const store = new WorkMemoryStore(d);

  const session = recordAiToolExchange(d, {
    tool: "ai_draft_reply",
    input: { conversationId: "conv-1" },
    response: { ok: false, draft: "needle-agent-history" },
  }) as { id: string };

  const sessionRow = d.prepare(`SELECT source FROM work_agent_sessions WHERE id=?`).get(session.id) as any;
  assert.equal(sessionRow.source, "ai_draft_reply");
  const count = (d.prepare(`SELECT COUNT(*) AS c FROM work_agent_messages WHERE session_id=?`).get(session.id) as any).c;
  assert.equal(count, 2);
  assert.equal(store.search("needle-agent-history").some((hit) => hit.kind === "message" && hit.session_id === session.id), true);
});
