import assert from "node:assert/strict";
import {
  installResolveHooks,
  toDataModuleUrl,
} from "./helpers/register-hooks-compat.mjs";

const extensionsShimSource = [
  "export const extension_settings = {};",
  "export function getContext() {",
  "  return globalThis.__stBmeTestContext || {",
  "    chat: [],",
  "    chatMetadata: {},",
  "    extensionSettings: {},",
  "    powerUserSettings: {},",
  "    characters: {},",
  "    characterId: null,",
  "    name1: '玩家',",
  "    name2: '',",
  "    chatId: 'test-chat',",
  "  };",
  "}",
].join("\n");

const scriptShimSource = [
  "export function getRequestHeaders() {",
  "  return {};",
  "}",
  "export function substituteParamsExtended(value) {",
  "  return String(value ?? '');",
  "}",
].join("\n");

const openAiShimSource = [
  "export const chat_completion_sources = {};",
  "export async function sendOpenAIRequest() {",
  "  throw new Error('sendOpenAIRequest should not be called in extractor-owner-scope test');",
  "}",
].join("\n");

installResolveHooks([
  {
    specifiers: [
      "../../../extensions.js",
      "../../../../extensions.js",
      "../../../../../extensions.js",
    ],
    url: toDataModuleUrl(extensionsShimSource),
  },
  {
    specifiers: [
      "../../../../script.js",
      "../../../../../script.js",
    ],
    url: toDataModuleUrl(scriptShimSource),
  },
  {
    specifiers: [
      "../../../../openai.js",
      "../../../../../openai.js",
    ],
    url: toDataModuleUrl(openAiShimSource),
  },
]);

const { createEmptyGraph, createNode, addNode } = await import("../graph/graph.js");
const { DEFAULT_NODE_SCHEMA } = await import("../graph/schema.js");
const { extractMemories } = await import("../maintenance/extractor.js");

function setTestOverrides(overrides = {}) {
  globalThis.__stBmeTestOverrides = overrides;
  return () => {
    delete globalThis.__stBmeTestOverrides;
  };
}

globalThis.__stBmeTestContext = {
  chat: [],
  chatMetadata: {},
  extensionSettings: {},
  powerUserSettings: {},
  characters: {},
  characterId: null,
  name1: "玩家",
  name2: "",
  chatId: "test-chat",
};

{
  const graph = createEmptyGraph();
  addNode(
    graph,
    createNode({
      type: "character",
      fields: { name: "艾琳" },
      seq: 1,
    }),
  );
  addNode(
    graph,
    createNode({
      type: "character",
      fields: { name: "露西亚" },
      seq: 1,
    }),
  );
  globalThis.__stBmeTestContext.name2 = "群像卡";
  const restore = setTestOverrides({
    llm: {
      async callLLMForJSON() {
        return {
          operations: [
            {
              action: "create",
              type: "pov_memory",
              fields: { summary: "有人觉得钟楼里还有问题" },
            },
          ],
          cognitionUpdates: [
            {
              knownRefs: ["evt-missing"],
            },
          ],
          regionUpdates: {},
        };
      },
    },
  });

  try {
    const result = await extractMemories({
      graph,
      messages: [{ seq: 3, role: "assistant", content: "多人场景测试" }],
      startSeq: 3,
      endSeq: 3,
      schema: DEFAULT_NODE_SCHEMA,
      embeddingConfig: null,
      settings: {},
    });

    assert.equal(result.success, true);
    assert.equal(
      graph.nodes.filter((node) => !node.archived && node.type === "pov_memory").length,
      0,
    );
    assert.ok(Array.isArray(result.ownerWarnings));
    assert.ok(
      result.ownerWarnings.some((warning) => warning.kind === "invalid-owner-scope"),
    );
  } finally {
    restore();
  }
}

{
  const graph = createEmptyGraph();
  addNode(
    graph,
    createNode({
      type: "character",
      fields: { name: "艾琳" },
      seq: 1,
    }),
  );
  globalThis.__stBmeTestContext.name2 = "艾琳";
  const restore = setTestOverrides({
    llm: {
      async callLLMForJSON() {
        return {
          operations: [
            {
              action: "create",
              type: "pov_memory",
              fields: { summary: "艾琳觉得钟楼里藏着第二条暗道" },
            },
          ],
          cognitionUpdates: [],
          regionUpdates: {},
        };
      },
    },
  });

  try {
    const result = await extractMemories({
      graph,
      messages: [{ seq: 5, role: "assistant", content: "单角色场景测试" }],
      startSeq: 5,
      endSeq: 5,
      schema: DEFAULT_NODE_SCHEMA,
      embeddingConfig: null,
      settings: {},
    });

    assert.equal(result.success, true);
    const povNode = graph.nodes.find(
      (node) => !node.archived && node.type === "pov_memory",
    );
    assert.ok(povNode);
    assert.equal(povNode.scope?.ownerType, "character");
    assert.equal(povNode.scope?.ownerName, "艾琳");
  } finally {
    restore();
  }
}

{
  const graph = createEmptyGraph();
  globalThis.__stBmeTestContext.name1 = "邱谁";
  globalThis.__stBmeTestContext.name2 = "群像卡";
  const restore = setTestOverrides({
    llm: {
      async callLLMForJSON() {
        return {
          operations: [
            {
              action: "create",
              type: "pov_memory",
              scope: {
                layer: "pov",
                ownerType: "character",
                ownerName: "邱 谁",
                ownerId: "邱 谁",
              },
              fields: { summary: "她感觉对方在试探自己的底线" },
            },
          ],
          cognitionUpdates: [
            {
              ownerType: "character",
              ownerName: "【邱谁】",
              knownRefs: [],
            },
          ],
          regionUpdates: {},
        };
      },
    },
  });

  try {
    const result = await extractMemories({
      graph,
      messages: [{ seq: 7, role: "assistant", content: "用户名误标测试" }],
      startSeq: 7,
      endSeq: 7,
      schema: DEFAULT_NODE_SCHEMA,
      embeddingConfig: null,
      settings: {},
    });

    assert.equal(result.success, true);
    const povNode = graph.nodes.find(
      (node) => !node.archived && node.type === "pov_memory",
    );
    assert.ok(povNode);
    assert.equal(povNode.scope?.ownerType, "user");
    assert.equal(povNode.scope?.ownerName, "邱谁");
    const knowledgeOwners = Object.values(graph.knowledgeState?.owners || {});
    assert.equal(
      knowledgeOwners.some(
        (entry) =>
          String(entry?.ownerType || "") === "character" &&
          String(entry?.ownerName || "") === "邱谁",
      ),
      false,
    );
    assert.equal(
      knowledgeOwners.some(
        (entry) =>
          String(entry?.ownerType || "") === "user" &&
          String(entry?.ownerName || "") === "邱谁",
      ),
      true,
    );
  } finally {
    restore();
  }
}

console.log("extractor-owner-scope tests passed");
