import test from "node:test";
import assert from "node:assert/strict";
import { writeNodeWithEdges, type GraphRelationship } from "../index.js";

interface StubTx {
  run(cypher: string, params?: Record<string, unknown>): Promise<{
    records: { get(field: string): unknown }[];
    summary?: { counters: { updates(): { relationshipsCreated: number } } };
  }>;
}

function makeStubSession(targetId: string) {
  const tx: StubTx = {
    async run(cypher: string, params: Record<string, unknown> = {}) {
      if (cypher.includes("RETURN elementId(t) AS id, labels(t) AS labels")) {
        const ids = (params.ids as string[]) ?? [];
        const records = ids
          .filter((id) => id === targetId)
          .map((id) => ({
            get(field: string) {
              if (field === "id") return id;
              if (field === "labels") return ["Account"];
              throw new Error(`unknown field ${field}`);
            },
          }));
        return { records };
      }
      if (cypher.includes("CREATE (n:")) {
        return {
          records: [
            {
              get(field: string) {
                if (field === "nodeId") return "new-element-id";
                if (field === "nodeLabels") return ["Conversation"];
                throw new Error(`unknown field ${field}`);
              },
            },
          ],
        };
      }
      if (cypher.includes("CREATE (a)-[") || cypher.includes("CREATE (b)-[")) {
        return {
          records: [],
          summary: {
            counters: {
              updates: () => ({ relationshipsCreated: 1 }),
            },
          },
        };
      }
      return { records: [] };
    },
  };
  return {
    async executeWrite<T>(work: (tx: StubTx) => Promise<T>): Promise<T> {
      return await work(tx);
    },
  };
}

const VALID_UUID = "12345678-9abc-def0-1234-56789abcdef0";
const TARGET_ID = "target-element-id";

const REL: GraphRelationship[] = [
  { type: "PART_OF", direction: "outgoing", targetNodeId: TARGET_ID },
];

function captureStderr(): { restore: () => void; output: () => string } {
  const original = process.stderr.write.bind(process.stderr);
  let buf = "";
  (process.stderr as { write: (chunk: string | Uint8Array) => boolean }).write =
    (chunk: string | Uint8Array): boolean => {
      buf += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
      return true;
    };
  return {
    restore: () => {
      process.stderr.write = original;
    },
    output: () => buf,
  };
}

test("rejects null accountId from non-system writer", async () => {
  const cap = captureStderr();
  try {
    await assert.rejects(
      () =>
        writeNodeWithEdges({
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          session: makeStubSession(TARGET_ID) as any,
          labels: ["Conversation"],
          props: { accountId: null },
          relationships: REL,
          createdBy: { agent: "test-agent", session: "s" },
          expectedAccountId: VALID_UUID,
        }),
      /invalid-account-id/,
    );
    cap.restore();
    assert.match(
      cap.output(),
      /\[graph-write\] reject reason=invalid-account-id accountId=missing writer=test-agent\b/,
    );
  } finally {
    cap.restore();
  }
});

test("rejects non-UUID accountId from non-system writer", async () => {
  const cap = captureStderr();
  try {
    await assert.rejects(
      () =>
        writeNodeWithEdges({
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          session: makeStubSession(TARGET_ID) as any,
          labels: ["Conversation"],
          props: { accountId: "evil-leak" },
          relationships: REL,
          createdBy: { agent: "test-agent", session: "s" },
          expectedAccountId: VALID_UUID,
        }),
      /invalid-account-id/,
    );
    cap.restore();
    assert.match(
      cap.output(),
      /\[graph-write\] reject reason=invalid-account-id accountId=evil-lea writer=test-agent\b/,
    );
  } finally {
    cap.restore();
  }
});

test("rejects UUID-shape accountId that does not match the expected one", async () => {
  const stranger = "ffffffff-1111-2222-3333-444444444444";
  const cap = captureStderr();
  try {
    await assert.rejects(
      () =>
        writeNodeWithEdges({
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          session: makeStubSession(TARGET_ID) as any,
          labels: ["Conversation"],
          props: { accountId: stranger },
          relationships: REL,
          createdBy: { agent: "test-agent", session: "s" },
          expectedAccountId: VALID_UUID,
        }),
      /invalid-account-id/,
    );
    cap.restore();
    assert.match(
      cap.output(),
      /\[graph-write\] reject reason=invalid-account-id accountId=ffffffff writer=test-agent\b/,
    );
  } finally {
    cap.restore();
  }
});

test("accepts matching accountId from non-system writer", async () => {
  const result = await writeNodeWithEdges({
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    session: makeStubSession(TARGET_ID) as any,
    labels: ["Conversation"],
    props: { accountId: VALID_UUID },
    relationships: REL,
    createdBy: { agent: "test-agent", session: "s" },
    expectedAccountId: VALID_UUID,
  });
  assert.equal(result.nodeId, "new-element-id");
  assert.equal(result.edgesCreated, 1);
});

test("system bootstrap exemption: bad accountId still proceeds", async () => {
  const result = await writeNodeWithEdges({
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    session: makeStubSession(TARGET_ID) as any,
    labels: ["Conversation"],
    // accountId is intentionally wrong; system bootstrap exempts it.
    props: { accountId: "not-a-uuid" },
    relationships: REL,
    createdBy: { agent: "system", source: "writeAdminUserAndPerson" },
    expectedAccountId: VALID_UUID,
  });
  assert.equal(result.nodeId, "new-element-id");
});
