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

const assert = require("node:assert");
const { chromium } = require("playwright");

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

async function main() {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext();
  const page = await context.newPage();
  const consoleErrors = [];
  const networkFailures = [];
  page.on("console", (message) => {
    if (message.type() === "error") consoleErrors.push(message.text());
  });
  page.on("requestfailed", (request) => {
    networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" });
  });

  await page.goto(`${BASE_URL}/auth`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
  const result = await page.evaluate(async () => {
    const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
    const sessionA = `mnote-oo-browser-a-${suffix}`;
    const sessionB = `mnote-oo-browser-b-${suffix}`;
    const tokenA = `token-a-${suffix}`;
    const tokenB = `token-b-${suffix}`;

    async function postJson(path, payload) {
      const response = await fetch(path, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(payload),
      });
      const body = await response.json().catch(() => null);
      return { status: response.status, body };
    }

    async function getJson(path) {
      const response = await fetch(path);
      const body = response.status === 204 ? null : await response.json().catch(() => null);
      return { status: response.status, body };
    }

    const registeredA = await postJson("/api/onlyoffice/bridge/session", {
      sessionId: sessionA,
      token: tokenA,
      editorType: "word",
      documentId: "doc_a",
      assetId: "asset_a",
      fileType: "docx",
      docKey: "doc_key_a",
      pageOrigin: location.origin,
    });
    const registeredB = await postJson("/api/onlyoffice/bridge/session", {
      sessionId: sessionB,
      token: tokenB,
      editorType: "word",
      documentId: "doc_b",
      assetId: "asset_b",
      fileType: "docx",
      docKey: "doc_key_b",
      pageOrigin: location.origin,
    });
    const wrongToken = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1`);

    const commandA = await postJson("/api/onlyoffice/bridge/commands", {
      sessionId: sessionA,
      token: tokenA,
      action: "document.insert_text",
      payload: { text: "A" },
    });
    const commandB = await postJson("/api/onlyoffice/bridge/commands", {
      sessionId: sessionB,
      token: tokenB,
      action: "document.insert_text",
      payload: { text: "B" },
    });

    const nextA = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1000`);
    const nextB = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&timeoutMs=1000`);

    const postedA = await postJson("/api/onlyoffice/bridge/results", {
      sessionId: sessionA,
      token: tokenA,
      id: nextA.body && nextA.body.id,
      ok: true,
      result: { text: "result-a" },
    });
    const postedB = await postJson("/api/onlyoffice/bridge/results", {
      sessionId: sessionB,
      token: tokenB,
      id: nextB.body && nextB.body.id,
      ok: true,
      result: { text: "result-b" },
    });

    const resultA = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&commandId=${encodeURIComponent(nextA.body && nextA.body.id)}&timeoutMs=1000`);
    const resultB = await getJson(`/api/onlyoffice/bridge/results?sessionId=${encodeURIComponent(sessionB)}&token=${encodeURIComponent(tokenB)}&commandId=${encodeURIComponent(nextB.body && nextB.body.id)}&timeoutMs=1000`);
    const sessions = await getJson("/api/onlyoffice/bridge/sessions");
    const currentBeforeClose = await getJson("/api/onlyoffice/bridge/session/current");
    const closeA = await postJson("/api/onlyoffice/bridge/session/close", {
      sessionId: sessionA,
      token: tokenA,
    });
    const nextAAfterClose = await getJson(`/api/onlyoffice/bridge/commands/next?sessionId=${encodeURIComponent(sessionA)}&token=${encodeURIComponent(tokenA)}&timeoutMs=1`);
    const sessionsAfterClose = await getJson("/api/onlyoffice/bridge/sessions");

    return {
      sessionA,
      sessionB,
      registeredA,
      registeredB,
      wrongToken,
      commandA,
      commandB,
      nextA,
      nextB,
      postedA,
      postedB,
      resultA,
      resultB,
      sessions,
      currentBeforeClose,
      closeA,
      nextAAfterClose,
      sessionsAfterClose,
    };
  });

  assert.equal(result.registeredA.status, 200, JSON.stringify(result.registeredA));
  assert.equal(result.registeredB.status, 200, JSON.stringify(result.registeredB));
  assert.equal(result.registeredA.body.docKey, "doc_key_a");
  assert.equal(result.registeredB.body.docKey, "doc_key_b");
  assert.equal(result.registeredA.body.pageOrigin, BASE_URL);
  assert.equal(result.registeredB.body.pageOrigin, BASE_URL);
  assert.equal(result.wrongToken.status, 401, JSON.stringify(result.wrongToken));
  assert.equal(result.commandA.status, 200, JSON.stringify(result.commandA));
  assert.equal(result.commandB.status, 200, JSON.stringify(result.commandB));
  assert.equal(result.nextA.status, 200, JSON.stringify(result.nextA));
  assert.equal(result.nextB.status, 200, JSON.stringify(result.nextB));
  assert.equal(result.nextA.body.action, "document.insert_text");
  assert.equal(result.nextB.body.action, "document.insert_text");
  assert.equal(result.nextA.body.payload.text, "A");
  assert.equal(result.nextB.body.payload.text, "B");
  assert.notEqual(result.nextA.body.id, result.nextB.body.id);
  assert.equal(result.postedA.status, 200, JSON.stringify(result.postedA));
  assert.equal(result.postedB.status, 200, JSON.stringify(result.postedB));
  assert.equal(result.resultA.status, 200, JSON.stringify(result.resultA));
  assert.equal(result.resultB.status, 200, JSON.stringify(result.resultB));
  assert.equal(result.resultA.body.result.text, "result-a");
  assert.equal(result.resultB.body.result.text, "result-b");
  const sessionARecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionA);
  const sessionBRecord = result.sessions.body.sessions.find((session) => session.sessionId === result.sessionB);
  assert(sessionARecord, JSON.stringify(result.sessions));
  assert(sessionBRecord, JSON.stringify(result.sessions));
  assert.equal(sessionARecord.docKey, "doc_key_a");
  assert.equal(sessionBRecord.pageOrigin, BASE_URL);
  assert.equal(result.currentBeforeClose.status, 200, JSON.stringify(result.currentBeforeClose));
  assert(result.currentBeforeClose.body.session, JSON.stringify(result.currentBeforeClose));
  assert.equal(result.closeA.status, 200, JSON.stringify(result.closeA));
  assert.equal(result.closeA.body.closed, true, JSON.stringify(result.closeA));
  assert.equal(result.nextAAfterClose.status, 401, JSON.stringify(result.nextAAfterClose));
  assert(!result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionA));
  assert(result.sessionsAfterClose.body.sessions.some((session) => session.sessionId === result.sessionB));

  await browser.close();
  console.log(JSON.stringify({
    ok: true,
    task: "task516-onlyoffice-bridge-multisession-browser-smoke",
    baseUrl: BASE_URL,
    sessionA: result.sessionA,
    sessionB: result.sessionB,
    wrongToken: { status: result.wrongToken.status, code: result.wrongToken.body && result.wrongToken.body.code },
    nextA: { id: result.nextA.body.id, text: result.nextA.body.payload.text },
    nextB: { id: result.nextB.body.id, text: result.nextB.body.payload.text },
    resultA: result.resultA.body.result,
    resultB: result.resultB.body.result,
    currentBeforeClose: {
      status: result.currentBeforeClose.status,
      sessionId: result.currentBeforeClose.body.session && result.currentBeforeClose.body.session.sessionId,
    },
    closeA: result.closeA.body,
    consoleErrors,
    networkFailures,
  }, null, 2));
}

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