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

const assert = require("node:assert");

const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");

async function requestJson(path, init = {}) {
  const response = await fetch(`${BASE_URL}${path}`, {
    ...init,
    headers: {
      "content-type": "application/json",
      "x-mnote-actor-id": "user_1",
      ...(init.headers || {}),
    },
  });
  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch (_) {
    payload = { raw: text };
  }
  return {
    status: response.status,
    headers: Object.fromEntries(response.headers.entries()),
    payload,
  };
}

function callPayload(sessionId, args, toolName = "mnote.onlyoffice.sheet.set_value") {
  return {
    toolName,
    workspaceId: "ws_demo",
    documentId: "doc_1",
    actorId: "user_1",
    sessionId,
    runId: `${sessionId}-run`,
    toolCallId: `${sessionId}-call`,
    traceId: `${sessionId}-trace`,
    idempotencyKey: `${sessionId}-idem`,
    dryRun: true,
    capabilityScope: ["office.write"],
    args,
  };
}

async function main() {
  const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
  const bridgeSessionId = `mnote-oo-http-${suffix}`;
  const bridgeToken = `token-${suffix}`;
  const register = await requestJson("/api/onlyoffice/bridge/session", {
    method: "POST",
    body: JSON.stringify({
      sessionId: bridgeSessionId,
      token: bridgeToken,
      editorType: "cell",
      documentId: "doc_http",
      assetId: "asset_http_allowed",
      fileType: "xlsx",
    }),
  });
  assert.equal(register.status, 200, JSON.stringify(register));
  assert.equal(register.payload.sessionId, bridgeSessionId);

  const implicit = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_implicit", {
      address: "A1",
      value: "after",
    })),
  });
  assert.equal(implicit.status, 400, JSON.stringify(implicit));
  assert.equal(implicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required");

  const forbidden = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_forbidden", {
      onlyofficeSessionId: bridgeSessionId,
      address: "A1",
      value: "after",
      aiAccessScope: {
        permissionLevel: "read_write",
        allowedResourceIds: ["asset_other"],
      },
    })),
  });
  assert.equal(forbidden.status, 403, JSON.stringify(forbidden));
  assert.equal(forbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden");

  const missingScope = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_missing_scope", {
      onlyofficeSessionId: bridgeSessionId,
      address: "A1",
      value: "after",
    })),
  });
  assert.equal(missingScope.status, 403, JSON.stringify(missingScope));
  assert.equal(missingScope.headers["x-error-code"], "mnote_onlyoffice_resource_scope_required");

  const allowed = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_allowed", {
      onlyofficeSessionId: bridgeSessionId,
      address: "A1",
      value: "after",
      aiAccessScope: {
        permissionLevel: "read_write",
        allowedResourceIds: ["asset_http_allowed"],
      },
    })),
  });
  assert.equal(allowed.status, 200, JSON.stringify(allowed));
  assert.equal(allowed.payload.result.schema, "mnote.onlyoffice.action_plan.v1");
  assert.equal(allowed.payload.result.sessionId, bridgeSessionId);
  assert.equal(allowed.payload.result.action, "sheet.set_value");
  assert.equal(allowed.payload.audit.effect, "dry_run");

  const currentImplicit = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_current_implicit", {
      aiAccessScope: {
        permissionLevel: "read",
        allowedResourceIds: ["asset_http_allowed"],
      },
    }, "mnote.onlyoffice.session.current")),
  });
  assert.equal(currentImplicit.status, 400, JSON.stringify(currentImplicit));
  assert.equal(currentImplicit.headers["x-error-code"], "mnote_onlyoffice_session_explicit_required");

  const currentForbidden = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_current_forbidden", {
      onlyofficeSessionId: bridgeSessionId,
      aiAccessScope: {
        permissionLevel: "read",
        allowedResourceIds: ["asset_other"],
      },
    }, "mnote.onlyoffice.session.current")),
  });
  assert.equal(currentForbidden.status, 403, JSON.stringify(currentForbidden));
  assert.equal(currentForbidden.headers["x-error-code"], "mnote_onlyoffice_resource_scope_forbidden");

  const currentAllowed = await requestJson("/api/hermes/tools/mnote/call", {
    method: "POST",
    body: JSON.stringify(callPayload("sess_http_current_allowed", {
      onlyofficeSessionId: bridgeSessionId,
      aiAccessScope: {
        permissionLevel: "read",
        allowedResourceIds: ["asset_http_allowed"],
      },
    }, "mnote.onlyoffice.session.current")),
  });
  assert.equal(currentAllowed.status, 200, JSON.stringify(currentAllowed));
  assert.equal(currentAllowed.payload.result.schema, "mnote.onlyoffice.session.v1");
  assert.equal(currentAllowed.payload.result.session.sessionId, bridgeSessionId);
  assert.equal(currentAllowed.payload.result.session.assetId, "asset_http_allowed");

  console.log(JSON.stringify({
    ok: true,
    task: "task515-onlyoffice-live-scope-http-smoke",
    baseUrl: BASE_URL,
    bridgeSessionId,
    implicit: { status: implicit.status, code: implicit.headers["x-error-code"] },
    forbidden: { status: forbidden.status, code: forbidden.headers["x-error-code"] },
    missingScope: { status: missingScope.status, code: missingScope.headers["x-error-code"] },
    allowed: {
      status: allowed.status,
      schema: allowed.payload.result.schema,
      action: allowed.payload.result.action,
      audit: allowed.payload.audit.effect,
    },
    currentImplicit: { status: currentImplicit.status, code: currentImplicit.headers["x-error-code"] },
    currentForbidden: { status: currentForbidden.status, code: currentForbidden.headers["x-error-code"] },
    currentAllowed: {
      status: currentAllowed.status,
      schema: currentAllowed.payload.result.schema,
      sessionId: currentAllowed.payload.result.session.sessionId,
      assetId: currentAllowed.payload.result.session.assetId,
    },
  }, null, 2));
}

main().catch((error) => {
  console.error(error && error.stack ? error.stack : error);
  process.exit(1);
});
