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

const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const { chromium } = require("playwright");

const TASK = "task431-vscode-explorer-dnd-readonly-conflict-smoke";
const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const AUTH_BASE_URL = (process.env.MNOTE_AUTH_BASE_URL || BASE_URL).replace(/\/+$/, "");
const CONVEX_URL = (process.env.NEXT_PUBLIC_CONVEX_URL || process.env.CONVEX_SELF_HOSTED_URL || "http://127.0.0.1:3210").replace(/\/+$/, "");
const e2ePassword = () => process.env.MNOTE_E2E_PASSWORD || ["Mnote", "E2E", "123!"].join("");
const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK);
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");

async function writeResult(payload) {
  await fs.mkdir(OUTPUT_DIR, { recursive: true });
  await fs.writeFile(RESULT_PATH, `${JSON.stringify({ ...payload, resultPath: RESULT_PATH }, null, 2)}\n`, "utf8");
}

async function requestJson(request, requestPath, init = {}) {
  const response = await request.fetch(`${BASE_URL}${requestPath}`, {
    ...init,
    headers: {
      ...(init.data !== undefined ? { "content-type": "application/json" } : {}),
      ...(init.headers || {}),
    },
    timeout: 20_000,
  });
  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch {
    payload = text;
  }
  if (!response.ok()) {
    throw new Error(`${requestPath} 请求失败: ${response.status()} ${typeof payload === "string" ? payload : JSON.stringify(payload)}`);
  }
  return payload;
}

async function requestAuthJson(request, requestPath, init = {}) {
  const response = await request.fetch(`${AUTH_BASE_URL}${requestPath}`, {
    ...init,
    headers: {
      ...(init.data !== undefined ? { "content-type": "application/json" } : {}),
      ...(init.headers || {}),
    },
    timeout: 20_000,
  });
  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch {
    payload = text;
  }
  if (!response.ok()) {
    throw new Error(`${requestPath} 请求失败: ${response.status()} ${typeof payload === "string" ? payload : JSON.stringify(payload)}`);
  }
  return payload;
}

async function requestJsonAllowError(request, requestPath, init = {}) {
  let response;
  try {
    response = await request.fetch(`${BASE_URL}${requestPath}`, {
      ...init,
      headers: {
        ...(init.data !== undefined ? { "content-type": "application/json" } : {}),
        ...(init.headers || {}),
      },
      timeout: 20_000,
    });
  } catch (error) {
    return {
      ok: false,
      status: 0,
      payload: { error: error instanceof Error ? error.message : String(error) },
    };
  }
  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch {
    payload = text;
  }
  return { ok: response.ok(), status: response.status(), payload };
}

async function convexCall(context, kind, convexPath, args) {
  const cookies = await context.cookies(BASE_URL);
  const jwt = cookies.find((cookie) => cookie.name === "__convexAuthJWT")?.value;
  assert(jwt, "缺少 __convexAuthJWT cookie");
  const response = await fetch(`${CONVEX_URL}/api/${kind}`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${jwt}`,
      "content-type": "application/json",
      "Convex-Client": TASK,
    },
    body: JSON.stringify({ path: convexPath, format: "convex_encoded_json", args: [args] }),
  });
  const body = await response.json();
  if (!response.ok || body.status !== "success") {
    throw new Error(`Convex ${kind} ${convexPath} 失败: ${response.status} ${JSON.stringify(body)}`);
  }
  return body.value;
}

async function createPage(request, workspaceId, title, parentId = null) {
  const data = {
    action: "create",
    parentId,
    title,
  };
  if (workspaceId) {
    data.workspaceId = workspaceId;
  }
  const payload = await requestJson(request, "/api/tree/commands", {
    method: "POST",
    data,
  });
  const result = payload.result || payload;
  assert(result.documentId, `创建页面失败: ${JSON.stringify(payload)}`);
  return result;
}

function docRow(documentId, title) {
  return {
    rowId: `doc:${documentId}`,
    rowKind: "doc",
    documentId,
    assetId: null,
    assetDocumentId: null,
    assetType: null,
    storagePath: null,
    title,
  };
}

async function runDropPreflight(request, payload) {
  return await requestJsonAllowError(request, "/api/tree/filetree/drop-preflight", {
    method: "POST",
    data: payload,
  });
}

async function cleanup(request, workspaceId, ids) {
  if (!workspaceId) return;
  for (const documentId of ids.filter(Boolean)) {
    await requestJson(request, "/api/tree/commands", {
      method: "POST",
      data: { action: "archive", workspaceId, documentId },
    }).catch(() => null);
  }
  await requestJson(request, "/api/documents/empty-trash", {
    method: "POST",
    data: { workspaceId },
  }).catch(() => null);
}

(async () => {
  const stamp = Date.now();
  const prefix = `TEST-10REVIEW-07-P7-DND-${stamp}`;
  const result = {
    ok: false,
    task: TASK,
    baseUrl: BASE_URL,
    authBaseUrl: AUTH_BASE_URL,
    convexUrl: CONVEX_URL,
    email: `mnote.stage7.dnd.${stamp}@example.com`,
    prefix,
    checks: [],
  };

  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext();
  const request = context.request;
  let workspaceId = null;
  const cleanupDocIds = [];

  try {
    await requestAuthJson(request, "/api/auth", {
      method: "POST",
      data: {
        action: "auth:signIn",
        args: {
          provider: "password",
          params: { email: result.email, password: e2ePassword(), flow: "signUp", name: `stage7-dnd-${stamp}` },
        },
      },
    });

    const currentUser = await convexCall(context, "query", "users:currentUser", {});
    assert(currentUser?._id, "缺少 Convex currentUser._id");
    const workspaces = await convexCall(context, "query", "workspaces:fetchWorkspaceSummaries", {});
    workspaceId = workspaces.activeWorkspaceId;
    const targetResult = await createPage(request, workspaceId, `${prefix}-target`);
    workspaceId = workspaceId || targetResult.workspaceId;
    assert(workspaceId, "缺少隔离 workspaceId");

    const target = targetResult.documentId;
    const source = (await createPage(request, workspaceId, `${prefix}-same-name`)).documentId;
    const existing = (await createPage(request, workspaceId, `${prefix}-same-name`, target)).documentId;
    cleanupDocIds.push(target, source, existing);

    const rows = [
      docRow(target, `${prefix}-target`),
      docRow(source, `${prefix}-same-name`),
      docRow(existing, `${prefix}-same-name`),
    ];
    const documentParents = [
      { documentId: target, parentId: null },
      { documentId: source, parentId: null },
      { documentId: existing, parentId: target },
    ];
    const basePayload = {
      workspaceId,
      copy: false,
      sourceCapabilities: ["read", "write", "move"],
      targetCapabilities: ["read", "write", "drop"],
      targetDocumentId: target,
      targetRowId: `doc:${target}`,
      focusedRowId: `doc:${target}`,
      activeDocumentId: target,
      rowIds: [`doc:${source}`],
      rows,
      targetChildren: [
        {
          rowKind: "doc",
          documentId: existing,
          assetId: null,
          title: `${prefix}-same-name`,
        },
      ],
      documentParents,
      conflictPolicy: "prompt",
    };

    const readonlyTarget = await runDropPreflight(request, {
      ...basePayload,
      targetCapabilities: ["read"],
      targetChildren: [],
    });
    assert.equal(readonlyTarget.ok, false, `readonly target 应被拒绝: ${JSON.stringify(readonlyTarget)}`);
    assert.match(JSON.stringify(readonlyTarget.payload), /只读|readonly|目标位置/);

    const readonlySource = await runDropPreflight(request, {
      ...basePayload,
      sourceCapabilities: ["read"],
      targetChildren: [],
    });
    assert.equal(readonlySource.ok, false, `readonly source 应被拒绝: ${JSON.stringify(readonlySource)}`);
    assert.match(JSON.stringify(readonlySource.payload), /只读|readonly|来源/);

    const conflict = await runDropPreflight(request, basePayload);
    assert.equal(conflict.ok, true, `同名冲突应返回确认计划: ${JSON.stringify(conflict)}`);
    assert.equal(conflict.payload?.plan?.requiresConfirmation, true);
    assert.equal(conflict.payload?.plan?.conflicts?.[0]?.title, `${prefix}-same-name`);
    assert.equal(conflict.payload?.plan?.conflicts?.[0]?.existingDocumentId, existing);

    const normalCopy = await runDropPreflight(request, {
      ...basePayload,
      copy: true,
      sourceCapabilities: ["read"],
      targetChildren: [],
    });
    assert.equal(normalCopy.ok, true, `copy modifier 不应被 readonly source move 规则误拒绝: ${JSON.stringify(normalCopy)}`);
    assert.equal(normalCopy.payload?.plan?.copy, true);
    assert.equal(normalCopy.payload?.plan?.requiresConfirmation ?? false, false);

    result.checks.push(
      { area: "readonly-target", ok: true, status: readonlyTarget.status },
      { area: "readonly-source", ok: true, status: readonlySource.status },
      { area: "conflict-confirmation-plan", ok: true, conflict: conflict.payload.plan.conflicts[0] },
      { area: "copy-modifier-no-false-positive", ok: true, plan: normalCopy.payload.plan },
    );
    result.workspaceId = workspaceId;
    result.fixture = { target, source, existing };
    result.ok = true;
  } catch (error) {
    result.error = error instanceof Error ? error.stack || error.message : String(error);
    process.exitCode = 1;
  } finally {
    await cleanup(request, workspaceId, cleanupDocIds).catch((error) => {
      result.cleanupError = error instanceof Error ? error.message : String(error);
    });
    await browser.close().catch(() => undefined);
    await writeResult(result);
  }
})();
