#!/usr/bin/env node
"use strict";

const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const { request } = require("playwright");

const TASK = "task557-page-ai-agent-descriptor-smoke";
const BASE_URL = (
  process.env.MNOTE_UI_BASE_URL ||
  process.env.MNOTE_WEB_SMOKE_BASE_URL ||
  "http://127.0.0.1:3000"
).replace(/\/+$/, "");
const ACTOR_ID = process.env.MNOTE_PAGE_AI_DESCRIPTOR_ACTOR_ID || `task557-descriptor-${Date.now().toString(36)}`;
const PROFILE = process.env.MNOTE_PAGE_AI_DESCRIPTOR_PROFILE || "task557-descriptor-smoke";
const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK);
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000);

function descriptorById(payload) {
  return new Map(
    (payload.descriptors || []).map((descriptor) => [
      descriptor.agentId,
      descriptor,
    ]),
  );
}

function arrayIncludes(value, expected, message) {
  assert(Array.isArray(value), `${message}: 不是数组`);
  assert(value.includes(expected), `${message}: 缺少 ${expected}`);
}

function toolsByName(descriptor) {
  return new Map(
    (descriptor.tools || []).map((tool) => [
      tool.name,
      tool,
    ]),
  );
}

async function loadDescriptors(api) {
  const response = await api.get(`/api/page-ai/agents/descriptors?profile=${encodeURIComponent(PROFILE)}`, {
    timeout: REQUEST_TIMEOUT_MS,
  });
  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch (error) {
    throw new Error(`descriptor response 不是 JSON: ${text.slice(0, 1000)}`);
  }
  assert(response.ok(), `descriptor API 失败: ${response.status()} ${text.slice(0, 1000)}`);
  return payload;
}

async function main() {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  const api = await request.newContext({
    baseURL: BASE_URL,
    extraHTTPHeaders: {
      "x-mnote-actor-id": ACTOR_ID,
      "x-mnote-actor-type": "user",
      accept: "application/json",
    },
  });

  try {
    const payload = await loadDescriptors(api);
    assert.equal(payload.schema, "mnote.ai_agent_descriptors.v1", "顶层 schema 不正确");
    assert.equal(payload.profile, PROFILE, "profile 应回显 query profile");
    assert.equal(payload.actorId, ACTOR_ID, "actorId 应使用请求 actor");
    assert(Array.isArray(payload.descriptors), "descriptors 必须是数组");
    assert.equal(payload.descriptors.length, 3, "应返回 Hermes / Reasonix / Chat-only 三类 agent");

    const descriptors = descriptorById(payload);
    for (const agentId of ["hermes", "reasonix", "chat_only"]) {
      assert(descriptors.has(agentId), `缺少 ${agentId} descriptor`);
      assert.equal(descriptors.get(agentId).schema, "mnote.ai_agent_descriptor.v1", `${agentId} descriptor schema 不正确`);
    }

    const hermes = descriptors.get("hermes");
    assert.equal(hermes.provider, "hermes_client", "Hermes provider 不正确");
    assert.equal(hermes.canWriteFiles, true, "Hermes 应允许文件写入能力声明");
    arrayIncludes(hermes.capabilities, "mnote_tools", "Hermes capabilities");
    assert(toolsByName(hermes).has("mnote.knowledge_rag.query"), "Hermes tools 应包含 knowledge_rag.query");

    const reasonix = descriptors.get("reasonix");
    assert.equal(reasonix.provider, "acp_reasonix", "Reasonix provider 不正确");
    assert.equal(reasonix.acpRuntime, "reasonix", "Reasonix acpRuntime 不正确");
    assert.equal(reasonix.canWriteFiles, true, "Reasonix 应允许文件写入能力声明");
    arrayIncludes(reasonix.capabilities, "native_patch", "Reasonix capabilities");
    arrayIncludes(reasonix.capabilities, "knowledge_rag", "Reasonix capabilities");
    const reasonixTools = toolsByName(reasonix);
    for (const toolName of [
      "mnote.knowledge_rag.status",
      "mnote.knowledge_rag.query",
      "mnote.knowledge_rag.open_reference",
    ]) {
      assert(reasonixTools.has(toolName), `Reasonix tools 缺少 ${toolName}`);
      assert.equal(reasonixTools.get(toolName).enabled, true, `${toolName} 默认应启用`);
    }
    assert(
      (reasonix.capabilityPacks || []).some((capability) => capability.id === "mnote-knowledge-rag" && capability.enabled === true),
      "Reasonix capabilityPacks 应包含启用的 mnote-knowledge-rag",
    );
    assert.equal(reasonix.capabilityStates?.knowledge_rag?.enabled, true, "Reasonix capabilityStates 应声明 knowledge_rag 默认启用");

    const chatOnly = descriptors.get("chat_only");
    assert.equal(chatOnly.provider, "chat_only", "Chat-only provider 不正确");
    assert.equal(chatOnly.canWriteFiles, false, "Chat-only 不应声明文件写入");
    assert.equal((chatOnly.tools || []).length, 0, "Chat-only 不应暴露 MNote tools");
    arrayIncludes(chatOnly.capabilities, "chat", "Chat-only capabilities");
    assert.deepEqual(chatOnly.defaultContextRefs || [], [], "Chat-only 默认不应携带 MNote context refs");

    const toggleResponse = await api.put("/api/hermes/client/capabilities/toggle", {
      timeout: REQUEST_TIMEOUT_MS,
      headers: { "content-type": "application/json" },
      data: {
        runtime: "mnote",
        profile: PROFILE,
        id: "mnote-knowledge-rag",
        enabled: false,
      },
    });
    const toggleText = await toggleResponse.text();
    assert(toggleResponse.ok(), `禁用 knowledge rag capability 失败: ${toggleResponse.status()} ${toggleText.slice(0, 1000)}`);
    const disabledPayload = await loadDescriptors(api);
    const disabledReasonix = descriptorById(disabledPayload).get("reasonix");
    assert.equal(disabledReasonix.capabilityStates?.knowledge_rag?.enabled, false, "禁用后 descriptor capabilityStates 应为 false");
    assert(!(disabledReasonix.capabilities || []).includes("knowledge_rag"), "禁用后 Reasonix capabilities 不应继续声明 knowledge_rag");
    assert((disabledReasonix.disabledCapabilities || []).includes("knowledge_rag"), "禁用后 disabledCapabilities 应包含 knowledge_rag");
    assert.equal(toolsByName(disabledReasonix).get("mnote.knowledge_rag.query")?.enabled, false, "禁用后 knowledge_rag.query tool 应不可用");

    const result = {
      ok: true,
      task: TASK,
      baseUrl: BASE_URL,
      profile: PROFILE,
      actorId: ACTOR_ID,
      agents: payload.descriptors.map((descriptor) => ({
        agentId: descriptor.agentId,
        provider: descriptor.provider,
        canWriteFiles: descriptor.canWriteFiles,
        capabilityCount: (descriptor.capabilities || []).length,
        toolCount: (descriptor.tools || []).length,
      })),
      disabledKnowledgeRagVerified: true,
    };
    fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8");
    console.log(JSON.stringify(result, null, 2));
  } finally {
    await api.dispose().catch(() => undefined);
  }
}

main().catch((error) => {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  fs.writeFileSync(
    path.join(OUTPUT_DIR, "failure.json"),
    `${JSON.stringify({ ok: false, task: TASK, error: error.stack || error.message || String(error) }, null, 2)}\n`,
    "utf8",
  );
  console.error(error.stack || error.message || String(error));
  process.exit(1);
});
