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

const assert = require("node:assert");
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const { chromium } = require("playwright");

const ROOT = process.env.MNOTE_REPO_ROOT || "/mnt/Data1T/mnote";
const { BASE_URL, UI_TIMEOUT_MS } = require(path.join(ROOT, "scripts", "tree-shell-smoke-helpers"));
const TASK = "task557-page-ai-run-resume-smoke";
const OUTPUT_DIR = path.join(ROOT, "tmp", TASK);
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const SCREENSHOT_PATH = path.join(OUTPUT_DIR, "page-ai-run-resume.png");
const CONTROL_PLANE_DB =
  process.env.MNOTE_CONTROL_PLANE_DB_PATH || "/mnt/Data1T/Mnote_data/control-plane/control-plane.db";
const CHROME = process.env.MNOTE_PAGE_AI_CHROME || "/usr/bin/google-chrome";
const RESUME_TIMEOUT_MS = Number(process.env.MNOTE_PAGE_AI_RESUME_TIMEOUT_MS || 90_000);
const ACTOR_ID = "mnote-e2e";
const WORKSPACE_ID = "local-ws:mnote-e2e:my-space";
const ROOT_PATH = "/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space";
const ROOT_URI = `file://${ROOT_PATH}`;
const OWNER_REL = "knowledge-rag-fixtures-7-57/PageAiRunResumeSmoke.md";
const DOCUMENT_ID = `local-md:${OWNER_REL.replaceAll("/", "~2F")}`;

function sqlQuote(value) {
  return `'${String(value).replaceAll("'", "''")}'`;
}

function sqliteExec(sql) {
  execFileSync("sqlite3", [CONTROL_PLANE_DB, sql], {
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"],
  });
}

function ensureFixture() {
  assert(fs.existsSync(CONTROL_PLANE_DB), `缺少 control-plane SQLite: ${CONTROL_PLANE_DB}`);
  const ownerPath = path.join(ROOT_PATH, OWNER_REL);
  fs.mkdirSync(path.dirname(ownerPath), { recursive: true });
  fs.writeFileSync(ownerPath, "# Page AI Run Resume Smoke\n\n用于验证 Page AI host run journal afterSeq 恢复。\n", "utf8");
  const now = new Date().toISOString();
  sqliteExec(`
    INSERT OR IGNORE INTO users (id, email, username, display_name, role, status, created_at, updated_at, revision)
    VALUES (${sqlQuote(ACTOR_ID)}, 'mnote.e2e@example.com', ${sqlQuote(ACTOR_ID)}, ${sqlQuote(ACTOR_ID)}, 'user', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
    INSERT OR REPLACE INTO workspaces (id, owner_user_id, name, kind, root_uri, root_path, source_kind, status, created_at, updated_at, revision)
    VALUES (${sqlQuote(WORKSPACE_ID)}, ${sqlQuote(ACTOR_ID)}, 'MNote E2E Space', 'personal', ${sqlQuote(ROOT_URI)}, ${sqlQuote(ROOT_PATH)}, 'local_folder', 'active', ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
    INSERT OR REPLACE INTO directory_grants (id, user_id, workspace_id, root_uri, root_path, permission, recursive, capabilities_json, source, status, created_by, created_at, updated_at, revision)
    VALUES ('grant_task557_page_ai_run_resume', ${sqlQuote(ACTOR_ID)}, ${sqlQuote(WORKSPACE_ID)}, ${sqlQuote(ROOT_URI)}, ${sqlQuote(ROOT_PATH)}, 'write', 1, '["ai"]', 'smoke', 'active', ${sqlQuote(ACTOR_ID)}, ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
  `);
}

function seedRun() {
  const stamp = Date.now();
  const runId = `run_task557_resume_${stamp}`;
  const sessionId = `mnote_task557_resume_${stamp}`;
  const now = new Date().toISOString();
  const runtimeJson = JSON.stringify({ runId, status: "running", lastEvent: "message.delta" });
  const payloadJson = JSON.stringify({
    requestId: `task557_resume_${stamp}`,
    agentId: "reasonix",
    message: "resume smoke user prompt",
  });
  sqliteExec(`
    INSERT OR REPLACE INTO ai_runtime_runs
      (id, user_id, workspace_id, document_id, session_id, run_id, title, profile, acp_runtime, trace_id, status, runtime_json, payload_json, deleted_at, created_at, updated_at, revision)
    VALUES
      (${sqlQuote(`arr_${stamp}`)}, ${sqlQuote(ACTOR_ID)}, ${sqlQuote(WORKSPACE_ID)}, ${sqlQuote(DOCUMENT_ID)}, ${sqlQuote(sessionId)}, ${sqlQuote(runId)}, 'Resume smoke', 'reasonix', 'reasonix', ${sqlQuote(`trace_task557_${stamp}`)}, 'running', ${sqlQuote(runtimeJson)}, ${sqlQuote(payloadJson)}, NULL, ${sqlQuote(now)}, ${sqlQuote(now)}, 1);
    DELETE FROM ai_runtime_events WHERE user_id=${sqlQuote(ACTOR_ID)} AND run_id=${sqlQuote(runId)};
    INSERT INTO ai_runtime_events
      (id, user_id, workspace_id, document_id, session_id, run_id, profile, acp_runtime, event_type, payload_json, created_at)
    VALUES
      (${sqlQuote(`are_${stamp}_1`)}, ${sqlQuote(ACTOR_ID)}, ${sqlQuote(WORKSPACE_ID)}, ${sqlQuote(DOCUMENT_ID)}, ${sqlQuote(sessionId)}, ${sqlQuote(runId)}, 'reasonix', 'reasonix', 'message.delta', '{"delta":"old-token"}', ${sqlQuote(now)}),
      (${sqlQuote(`are_${stamp}_2`)}, ${sqlQuote(ACTOR_ID)}, ${sqlQuote(WORKSPACE_ID)}, ${sqlQuote(DOCUMENT_ID)}, ${sqlQuote(sessionId)}, ${sqlQuote(runId)}, 'reasonix', 'reasonix', 'message.delta', '{"delta":"resumed-token"}', ${sqlQuote(now)});
  `);
  return { runId, sessionId };
}

async function signIn(context) {
  const response = await context.request.post(`${BASE_URL}/api/auth`, {
    data: {
      action: "auth:signIn",
      args: {
        provider: "password",
        params: {
          account: ACTOR_ID,
          password: process.env.MNOTE_E2E_PASSWORD || "MnoteE2E123!",
          flow: "signIn",
        },
      },
    },
    timeout: UI_TIMEOUT_MS,
  });
  assert(response.ok(), `/api/auth 登录失败: ${response.status()} ${await response.text()}`);
}

async function main() {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  ensureFixture();
  const seeded = seedRun();
  const browser = await chromium.launch({
    headless: process.env.MNOTE_PAGE_AI_VERIFY_HEADED !== "1",
    executablePath: fs.existsSync(CHROME) ? CHROME : undefined,
  });
  const context = await browser.newContext({ viewport: { width: 1360, height: 900 } });
  const page = await context.newPage();
  const pageErrors = [];
  const consoleErrors = [];
  const apiRequests = [];
  const apiResponses = [];
  page.on("pageerror", (error) => pageErrors.push(String(error?.message || error)));
  page.on("console", (message) => {
    if (["error", "warning"].includes(message.type())) consoleErrors.push(`${message.type()}: ${message.text()}`);
  });
  page.on("request", (request) => {
    if (/\/api\/(page-ai|hermes\/client)/.test(request.url())) {
      apiRequests.push({ method: request.method(), url: request.url() });
    }
  });
  page.on("response", (response) => {
    if (/\/api\/(page-ai|hermes\/client)/.test(response.url())) {
      apiResponses.push({ status: response.status(), url: response.url() });
    }
  });
  try {
    await signIn(context);
    await page.addInitScript(({ documentId, runId, sessionId }) => {
      window.localStorage.setItem(`hermes_page_ai_session:${documentId}:active-run`, JSON.stringify({
        schema: "mnote.page_ai_active_run_snapshot.v1",
        hostRunId: runId,
        sessionId,
        lastSeq: "000000000000000001",
        status: "running",
        createdAt: Date.now(),
        updatedAt: Date.now(),
      }));
    }, { documentId: DOCUMENT_ID, runId: seeded.runId, sessionId: seeded.sessionId });

    const documentUrl = new URL(`${BASE_URL}/documents/${encodeURIComponent(DOCUMENT_ID)}`);
    documentUrl.searchParams.set("sourceKind", "local_folder");
    documentUrl.searchParams.set("rootUri", ROOT_URI);
    documentUrl.searchParams.set("workspaceId", WORKSPACE_ID);
    await page.goto(documentUrl.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS });
    try {
      await page.waitForFunction(
        (runId) => document.documentElement.getAttribute("data-mnote-page-ai-run-journal-resumed") === runId,
        seeded.runId,
        { timeout: RESUME_TIMEOUT_MS },
      );
      await page.waitForFunction(
        () => document.documentElement.getAttribute("data-mnote-page-ai-active-run-last-seq") === "000000000000000002",
        null,
        { timeout: RESUME_TIMEOUT_MS },
      );
    } catch (error) {
      const debugState = await page.evaluate(() => {
        const drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
        return {
          attrs: Object.fromEntries(Array.from(document.documentElement.attributes).map((attr) => [attr.name, attr.value])),
          drawerHidden: drawer ? drawer.hidden : null,
          drawerText: drawer ? (drawer.textContent || "").slice(0, 1000) : "",
          storageKeys: Object.keys(window.localStorage || {}).filter((key) => key.includes("page_ai") || key.includes("hermes_page_ai")),
        };
      }).catch((stateError) => ({ stateError: String(stateError) }));
      debugState.apiRequests = apiRequests;
      debugState.apiResponses = apiResponses;
      debugState.pageErrors = pageErrors;
      debugState.consoleErrors = consoleErrors;
      await page.screenshot({ path: path.join(OUTPUT_DIR, "failure.png"), fullPage: true }).catch(() => undefined);
      fs.writeFileSync(path.join(OUTPUT_DIR, "failure-state.json"), `${JSON.stringify(debugState, null, 2)}\n`, "utf8");
      throw error;
    }
    const state = await page.evaluate((runId) => {
      const drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]');
      const text = drawer ? drawer.textContent || "" : "";
      return {
        resumedRunId: document.documentElement.getAttribute("data-mnote-page-ai-run-journal-resumed") || "",
        activeHostRunId: document.documentElement.getAttribute("data-mnote-page-ai-active-host-run-id") || "",
        lastSeq: document.documentElement.getAttribute("data-mnote-page-ai-active-run-last-seq") || "",
        hasResumedToken: text.includes("resumed-token"),
        hasSkippedToken: text.includes("old-token"),
        runStatus: document.documentElement.getAttribute("data-mnote-page-ai-run-status") || "",
        drawerText: text.slice(0, 500),
        expectedRunId: runId,
      };
    }, seeded.runId);
    await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
    assert.equal(state.resumedRunId, seeded.runId, `未标记 journal resumed: ${JSON.stringify(state, null, 2)}`);
    assert.equal(state.activeHostRunId, seeded.runId, `active host run id 不匹配: ${JSON.stringify(state, null, 2)}`);
    assert.equal(state.lastSeq, "000000000000000002", `lastSeq 未推进: ${JSON.stringify(state, null, 2)}`);
    assert.equal(state.hasResumedToken, true, `未渲染 afterSeq 后的新事件: ${JSON.stringify(state, null, 2)}`);
    assert.equal(state.hasSkippedToken, false, `重复渲染了 afterSeq 之前的事件: ${JSON.stringify(state, null, 2)}`);
    assert.deepEqual(pageErrors, [], `页面异常: ${pageErrors.join("\n")}`);
    const result = {
      ok: true,
      task: TASK,
      baseUrl: BASE_URL,
      documentId: DOCUMENT_ID,
      runId: seeded.runId,
      sessionId: seeded.sessionId,
      state,
      consoleErrors,
      apiRequests,
      apiResponses,
      screenshot: SCREENSHOT_PATH,
    };
    fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8");
    console.log(JSON.stringify(result, null, 2));
  } finally {
    await browser.close().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);
});
