/* WITNESS · mosaic_v2-split-epay   (ledger W-071)
 *
 * What the journey PROMISES two diners, in my words:
 *   We ate together. One of us asks to split the bill, the other is invited, and each of us
 *   can settle our half on our own phone.
 *
 * I drive that as two people on two iPhones. Where I stop, and why:
 *   - I DO press `split-confirm` ("Split 2 ways") and, on the second phone,
 *     `split-invitation-accept`. Neither takes money — the first sends an invitation, the
 *     second accepts it — but BOTH change the table's settlement state, and I say so plainly
 *     rather than pretending this walk was read-only. Without them there is no split to look at.
 *   - I do NOT press anything that pays: not `split-invitation-pay-table`, not
 *     `split-invitation-pay-own`, not `payhow-pay-now`, not `payhow-server`. Live Stripe
 *     test merchant.
 *
 * The question I am here to answer: how far can each diner get toward paying their half, and
 * what are they SHOWN on the way — above all, is either of them ever told how much their
 * half actually is before being asked how to pay it?
 *
 * Falsification I owe: every money check below is scoped to the sheet's own container, never
 * the page, because the menu shows through behind every overlay in this app. If a figure
 * appears inside the sheet, the finding is wrong and I retract it.
 */
import { test, expect } from "@playwright/test";
import { journal, look, arriveAsDiner, roleContext, closeRole,
         PATRON, DWELL } from "../lib-witness.mjs";
import fs from "node:fs";

const TABLE = 9973;
const SLUG  = "mosaic_v2-split-epay";
const MONEY = /\$\s?\d+[.,]\d{2}/;

const sheetOf = (page, sel) => page.evaluate((s) => {
  const el = document.querySelector(`[data-testid="${s}"]`);
  return el ? el.innerText.replace(/\s+/g, " ").trim() : null;
}, sel);
const idsIn = (page, sel) => page.evaluate((s) => {
  const root = document.querySelector(`[data-testid="${s}"]`) || document;
  return [...new Set([...root.querySelectorAll("[data-testid]")]
    .filter(e => e.getBoundingClientRect().height > 0)
    .map(e => e.getAttribute("data-testid")))].sort();
}, sel);

test(SLUG, async ({ browser }) => {
  const fx  = JSON.parse(fs.readFileSync("/workspace/uat/runs/wave0/fixtures.json", "utf8"));
  const rid = fx["mosaic_v2-split"];
  if (!rid) throw new Error("no mosaic_v2-split fixture id");

  const jr = journal(SLUG);
  jr.target({ app: "patron (mosaic_v2)", baseUrl: PATRON, restaurantId: rid, table: TABLE });
  jr.note("**Two real phones, two people.** Both are iPhone 14 Pro contexts with their own "
        + "video; neither knows about the other except through the product.");
  jr.note("**What I pressed that changes state, declared up front:** `split-confirm` on phone 1 "
        + "and `split-invitation-accept` on phone 2. Neither moves money, both move the table's "
        + "settlement state, and without them there is no split to witness. **I pressed nothing "
        + "that pays** — no `split-invitation-pay-*`, no `payhow-*`.");

  const c1 = await roleContext(browser, jr, "diner-1");
  const c2 = await roleContext(browser, jr, "diner-2");
  const p1 = await c1.newPage(), p2 = await c2.newPage();

  const calls = [];
  for (const [tag, pg] of [["p1", p1], ["p2", p2]])
    pg.on("response", r => { if (/payment|settle|cover|split/i.test(r.url()))
      calls.push(`${tag} ${r.request().method()} ${r.status()} ${r.url().replace(/^.*\/dev/, "").split("?")[0]}`); });

  let verdict = "INCOMPLETE", why = "", ways = "?";

  try {
    await jr.step("DINER 1", "I sit down and put a drink in the basket", async id => {
      await arriveAsDiner(p1, rid, TABLE, "mosaic_v2");
      const add = p1.locator('[data-testid^="m2-add-"]').first();
      await expect(add).toBeVisible({ timeout: 25000 });
      await add.evaluate(e => e.click());
      await p1.waitForTimeout(DWELL);
      const t = await look(p1, jr, "diner-1", id, "diner 1 has a drink");
      return `In. ${t.slice(0, 100)}`;
    });

    await jr.step("DINER 2", "my friend scans the same code and adds a different drink", async id => {
      await arriveAsDiner(p2, rid, TABLE, "mosaic_v2");
      const adds = p2.locator('[data-testid^="m2-add-"]');
      await expect(adds.first()).toBeVisible({ timeout: 25000 });
      await adds.nth(1).evaluate(e => e.click());
      await p2.waitForTimeout(DWELL);
      const t = await look(p2, jr, "diner-2", id, "diner 2 has a drink");
      return `She is on the table. ${t.slice(0, 100)}`;
    });

    await jr.step("DINER 1", "I send our order to the kitchen", async id => {
      await p1.waitForTimeout(6000);
      await p1.locator('[data-testid="m2-view-order"], [data-testid="m2-dock-cart"]').first()
             .evaluate(e => e.click()).catch(() => {});
      await p1.waitForTimeout(DWELL);
      const chips = await p1.evaluate(() => [...document.querySelectorAll('[data-testid^="cart-scope-chip"]')]
        .map(e => e.innerText.replace(/\s+/g, " ").trim()));
      await look(p1, jr, "diner-1", id, "our shared basket");
      const now = p1.locator('[data-testid="order-now-btn"]').first();
      await expect(now, "there should be a way to place the order").toBeVisible({ timeout: 25000 });
      await now.evaluate(e => e.click());
      await p1.waitForTimeout(DWELL + 6000);
      return `Basket header: ${chips.length ? chips.map(c => `"${c}"`).join(", ") : "no per-person chips"}. Order sent.`;
    });

    await jr.step("DINER 1", "I ask for the check and say we want to split it", async id => {
      await p1.reload({ waitUntil: "domcontentloaded" });
      await p1.waitForTimeout(DWELL + 4000);
      await p1.locator('[data-testid="layout-service-button"]').first().evaluate(e => e.click()).catch(() => {});
      await p1.waitForTimeout(DWELL);
      const bill = p1.locator('[data-testid="service-bubble-ready_for_bill"]').first();
      await expect(bill, "Check Please should be offered").toBeVisible({ timeout: 20000 });
      await bill.evaluate(e => e.click());
      await p1.waitForTimeout(DWELL + 4000);
      await look(p1, jr, "diner-1", id, "how would you like to settle");
      const s   = await sheetOf(p1, "service-bubble");
      const ids = await idsIn(p1, "service-bubble");
      jr.note(`> **The settle sheet:** ${s}`);
      jr.note(`> **Controls on it:** \`${ids.join("`, `")}\``);
      const split = p1.locator('[data-testid="settlement-split"]').first();
      await expect(split, "a Split option should be offered when two of us are on the table")
        .toBeVisible({ timeout: 20000 });
      const splitT = (await split.innerText()).replace(/\s+/g, " ");
      return `It offers “${splitT}”. Whole sheet: "${s}". `
           + (MONEY.test(s || "") ? `Shows ${(s.match(MONEY)||[])[0]}.` : `No money figure in it.`);
    });

    await jr.step("DINER 1", "I choose Split, and it asks how many ways", async id => {
      await p1.locator('[data-testid="settlement-split"]').first().evaluate(e => e.click());
      await p1.waitForTimeout(DWELL + 2000);
      await look(p1, jr, "diner-1", id, "how many ways");
      const s   = await sheetOf(p1, "service-bubble");
      const ids = await idsIn(p1, "service-bubble");
      const confirm = p1.locator('[data-testid="split-confirm"]').first();
      await expect(confirm, "there should be a way to confirm the split").toBeVisible({ timeout: 20000 });
      ways = (await confirm.innerText()).replace(/\s+/g, " ");
      jr.note(`> **The split stepper:** ${s}`);
      jr.note(`> **Controls:** \`${ids.join("`, `")}\``);
      return `A stepper, defaulting to “${ways}”. Sheet: "${s}". `
           + (MONEY.test(s || "")
              ? `It tells me the half is ${(s.match(MONEY)||[])[0]}.`
              : `It does not tell me what a half comes to — I am splitting a number I have `
              + `never been shown.`);
    });

    await jr.step("DINER 1", "I confirm the split — this invites her, and I say so plainly",
      async id => {
        await p1.locator('[data-testid="split-confirm"]').first().evaluate(e => e.click());
        await p1.waitForTimeout(DWELL + 5000);
        const t = await look(p1, jr, "diner-1", id, "my phone right after confirming the split");
        const ids = await p1.evaluate(() => [...new Set([...document.querySelectorAll("[data-testid]")]
          .filter(e => e.getBoundingClientRect().height > 0)
          .map(e => e.getAttribute("data-testid"))
          .filter(x => /settle|payhow|cover|split|invitation|pay|order-status/i.test(x)))].sort());
        return `Confirmed “${ways}”. My own phone now shows: \`${ids.join("`, `") || "nothing about the split"}\`. `
             + `Screen: ${t.slice(0, 160)}`;
      });

    await jr.step("DINER 2", "her phone should now be telling her she has been invited to split",
      async id => {
        await p2.reload({ waitUntil: "domcontentloaded" });
        await p2.waitForTimeout(DWELL + 6000);
        await look(p2, jr, "diner-2", id, "the split invitation on her phone");
        const inv = p2.locator('[data-testid="split-invitation"]').first();
        await expect(inv, "she should be told about the split on her own screen")
          .toBeVisible({ timeout: 25000 });
        const s   = await sheetOf(p2, "split-invitation");
        const ids = await idsIn(p2, "split-invitation");
        jr.note(`> **Her invitation, word for word:** ${s}`);
        jr.note(`> **What she is offered:** \`${ids.join("`, `")}\``);
        return `She is told: "${s}". Her options: ${ids.filter(x => x.startsWith("split-invitation-")).join(", ")}. `
             + (MONEY.test(s || "")
                ? `It names her share: ${(s.match(MONEY)||[])[0]}.`
                : `It never names her share — she is asked to agree to a half whose size she `
                + `has not been told.`);
      });

    await jr.soft("DINER 2", "she accepts the split — no money moves, but the table state does",
      async id => {
        const acc = p2.locator('[data-testid="split-invitation-accept"]').first();
        if (!(await acc.count())) throw new Error("There is no Accept control on her invitation.");
        await acc.evaluate(e => e.click());
        await p2.waitForTimeout(DWELL + 5000);
        const t = await look(p2, jr, "diner-2", id, "her phone after accepting");
        const ids = await p2.evaluate(() => [...new Set([...document.querySelectorAll("[data-testid]")]
          .filter(e => e.getBoundingClientRect().height > 0)
          .map(e => e.getAttribute("data-testid"))
          .filter(x => /settle|payhow|cover|split|invitation|pay|pyc-/i.test(x)))].sort());
        const anyMoney = await p2.evaluate(() => {
          const el = document.querySelector('[data-testid="split-invitation"], [data-testid="service-bubble"]');
          return el ? el.innerText.replace(/\s+/g, " ").trim() : null;
        });
        return `After accepting she is on: \`${ids.join("`, `") || "no settlement surface at all"}\`. `
             + (anyMoney ? `The sheet reads: "${anyMoney}". ` : `No settlement sheet is on screen. `)
             + `Screen: ${t.slice(0, 140)}`;
      });

    await jr.soft("DINER 1", "and my phone — does it tell me she agreed?", async id => {
      await p1.reload({ waitUntil: "domcontentloaded" });
      await p1.waitForTimeout(DWELL + 5000);
      const t = await look(p1, jr, "diner-1", id, "my phone after she accepted");
      const ids = await p1.evaluate(() => [...new Set([...document.querySelectorAll("[data-testid]")]
        .filter(e => e.getBoundingClientRect().height > 0)
        .map(e => e.getAttribute("data-testid"))
        .filter(x => /settle|payhow|cover|split|invitation|pay|pyc-/i.test(x)))].sort());
      // Ask for the check again and see what the app now says to the person who started this.
      await p1.locator('[data-testid="layout-service-button"]').first().evaluate(e => e.click()).catch(() => {});
      await p1.waitForTimeout(DWELL);
      const drawer = await sheetOf(p1, "service-bubble");
      await look(p1, jr, "diner-1", id + "b", "asking for the check again");
      return `My phone shows \`${ids.join("`, `") || "nothing about the split"}\`. Re-opening the `
           + `service drawer reads: "${drawer}". Home screen: ${t.slice(0, 120)}`;
    });

    verdict = "WALKED";
    why = "Drove a real two-person split from ordering to an accepted invitation and stopped "
        + "at the payment boundary by choice, not because the product stopped me.";
  } catch (e) {
    why = String(e.message || e).split("\n")[0];
  } finally {
    jr.note(`> **Payment-ish network calls:** ${calls.length ? calls.join(" · ") : "none"}`);
    jr.note(`> **What I left behind:** table ${TABLE} carries a live session with two guests, one `
          + `placed order, an open check request, and a CONFIRMED 2-way split that diner 2 has `
          + `accepted and neither of us paid. Somebody will have to clear that table. No card `
          + `was ever entered and no money moved.`);
    jr.finish(verdict, why);
    await closeRole(c1, jr, "diner-1");
    await closeRole(c2, jr, "diner-2");
  }
});
