/* WITNESS · mosaic_v2-electronic-pay — pass 2, TWO diners   (ledger W-043)
 *
 * Pass 1 (table 9971, kept under pass1-solo-table9971/) drove this fixture SOLO and found
 * something I did not expect, so this pass exists to see the other shape:
 *
 *   a lone diner who taps "Check Please" is taken in ONE step to
 *   "You're covering the whole table / Almost there. / Pay now on my phone /
 *    Have my server bring it" — no Cover-or-Split choice at all, the app having already
 *   decided he is covering the whole table, with the staff already notified and NO amount
 *   anywhere on the screen.
 *
 * The canary journey is a TWO-diner one, so this pass puts a second person on the table and
 * walks the same path. The question is the same both times: how far can a diner get toward
 * paying, and what is he SHOWN on the way?
 *
 * I press nothing that pays — not `payhow-pay-now`, not `payhow-server`. The pay-how fork
 * is where this walk deliberately ends.
 *
 * Falsification I owe the "asked HOW before told WHAT" finding: if any settle screen shows
 * a money figure INSIDE the sheet (not the menu behind it) before the pay-how fork, the
 * finding is wrong and I retract it. So every money check below is scoped to `service-bubble`,
 * which pass 1 established is the sheet's own container.
 */
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 = 9972;
const SLUG  = "mosaic_v2-electronic-pay";
const MONEY = /\$\s?\d+[.,]\d{2}/;

/* Read the SHEET, never the page. The menu shows through behind every overlay in this app,
   so a page-wide substring would happily "find" a price that belongs to a dish tile. */
const sheet = (page) => page.evaluate(() => {
  const el = document.querySelector('[data-testid="service-bubble"]');
  return el ? el.innerText.replace(/\s+/g, " ").trim() : null;
});
const settlementIds = (page) => page.evaluate(() => {
  const el = document.querySelector('[data-testid="service-bubble"]');
  const root = el || document;
  return [...new Set([...root.querySelectorAll("[data-testid]")]
    .filter(e => e.getBoundingClientRect().height > 0)
    .map(e => e.getAttribute("data-testid")))].sort();
});
async function openCheck(page) {
  await page.locator('[data-testid="layout-service-button"]').first()
           .evaluate(e => e.click()).catch(() => {});
  await page.waitForTimeout(DWELL);
  const bill = page.locator('[data-testid="service-bubble-ready_for_bill"]').first();
  await expect(bill, "the service drawer should offer Check Please").toBeVisible({ timeout: 20000 });
  await bill.evaluate(e => e.click());
  await page.waitForTimeout(DWELL + 4000);
}

test(SLUG, async ({ browser }) => {
  const fx  = JSON.parse(fs.readFileSync("/workspace/uat/runs/wave0/fixtures.json", "utf8"));
  const rid = fx["mosaic_v2-electronic-pay"];
  const jr  = journal(SLUG);
  jr.target({ app: "patron (mosaic_v2)", baseUrl: PATRON, restaurantId: rid, table: TABLE,
              passes: ["pass1-solo-table9971 (one diner)", "this pass, table 9972 (two diners)"] });

  jr.note("**Two passes.** Pass 1 drove this fixture with ONE diner on table 9971 and is kept "
        + "whole under `pass1-solo-table9971/` (notes, screenshots, video, trace). This pass "
        + "puts a second person on the table, because the canary journey is a two-diner one.");
  jr.note("> **What pass 1 found, in one line:** a lone diner who taps *Check Please* is taken "
        + "straight to *“You’re covering the whole table / Almost there.”* with **Pay now on my "
        + "phone** and **Have my server bring it** — no Cover-or-Split choice, the staff already "
        + "notified, and **no amount on the screen**. Screenshot: "
        + "`pass1-solo-table9971/diner/06-how-would-you-like-to-settle.png`.");
  jr.note("**I press nothing that pays.** Not `payhow-pay-now`, not `payhow-server`. Live Stripe "
        + "test merchant; both controls move the table's settlement state.");

  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 = [];
  p1.on("response", r => { if (/payment|settle|cover|split/i.test(r.url()))
    calls.push(`${r.request().method()} ${r.status()} ${r.url().replace(/^.*\/dev/, "").split("?")[0]}`); });

  let verdict = "INCOMPLETE", why = "", forkShape = "unknown";

  try {
    await jr.step("DINER 1", "I sit down and put a drink in my 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 drink too", 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 too. ${t.slice(0, 100)}`;
    });

    await jr.step("DINER 1", "my phone should now show me there are two of us", 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 t = await look(p1, jr, "diner-1", id, "our shared basket");
      const chips = await p1.evaluate(() => [...document.querySelectorAll('[data-testid^="cart-scope-chip"]')]
        .map(e => e.innerText.replace(/\s+/g, " ").trim()));
      return chips.length
        ? `My basket header shows: ${chips.map(c => `"${c}"`).join(", ")}. Two of us.`
        : `No per-person chips in my basket at all — it reads: ${t.slice(0, 160)}`;
    });

    await jr.step("DINER 1", "I send the order to the kitchen for both of us", async id => {
      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);
      const t = await look(p1, jr, "diner-1", id, "order placed");
      return `Sent. ${t.slice(0, 150)}`;
    });

    await jr.step("DINER 1", "now I ask for the check — and I expect to be asked how to split it",
      async id => {
        await p1.reload({ waitUntil: "domcontentloaded" });
        await p1.waitForTimeout(DWELL + 4000);
        await openCheck(p1);
        await look(p1, jr, "diner-1", id, "what asking for the check gives me");
        const ids = await settlementIds(p1);
        const s   = await sheet(p1);
        forkShape = ids.includes("settlement-cover") ? "cover-or-split fork"
                  : ids.includes("payhow-pay-now")   ? "straight to the pay-how fork"
                  : "neither";
        jr.note(`> **The sheet, word for word:** ${s}`);
        jr.note(`> **Controls inside the sheet:** \`${ids.join("`, `")}\``);
        return `I got ${forkShape}. Sheet: "${s}". `
             + (MONEY.test(s || "") ? `It shows me ${(s.match(MONEY)||[])[0]}.`
                                    : `Not one money figure inside the sheet.`);
      });

    await jr.soft("DINER 1", "if I am offered a choice, I choose to cover the whole table",
      async id => {
        const cover = p1.locator('[data-testid="settlement-cover"]').first();
        if (!(await cover.count()))
          throw new Error("I was never offered a Cover-or-Split choice — the app had already "
                        + "decided for me. Nothing to choose.");
        const s0 = await sheet(p1);
        await cover.evaluate(e => e.click());
        await p1.waitForTimeout(DWELL + 2000);
        await look(p1, jr, "diner-1", id, "who am I covering");
        const s1 = await sheet(p1);
        const ids = await settlementIds(p1);
        jr.note(`> **Cover-scope sheet:** ${s1}`);
        const whole = p1.locator('[data-testid="cover-scope-table"]').first();
        await expect(whole, "a whole-table scope should be offered").toBeVisible({ timeout: 20000 });
        await whole.evaluate(e => e.click());
        await p1.waitForTimeout(DWELL + 3000);
        return `Fork said "${s0}". Then it asked ${ids.filter(x=>x.startsWith("cover-scope")).join(", ")} `
             + `— "${s1}". I picked the whole table.`;
      });

    await jr.step("DINER 1", "and here the app asks me HOW to pay — this is as far as I go",
      async id => {
        await look(p1, jr, "diner-1", id, "how do you want to pay");
        const now = p1.locator('[data-testid="payhow-pay-now"]').first();
        await expect(now, "a pay-on-my-phone option should be here").toBeVisible({ timeout: 20000 });
        const s   = await sheet(p1);
        const nowT = (await now.innerText()).replace(/\s+/g, " ");
        const srv  = p1.locator('[data-testid="payhow-server"]').first();
        const srvT = (await srv.count()) ? (await srv.innerText()).replace(/\s+/g, " ") : "(absent)";
        jr.note(`> **The pay-how fork, word for word:** ${s}`);
        jr.note(`> \`payhow-pay-now\` → “${nowT}”`);
        jr.note(`> \`payhow-server\` → “${srvT}”`);
        jr.note("> **I stop here on purpose.** Either control is a settlement action against a "
              + "live Stripe test merchant. My brief forbids pressing them, so the boundary "
              + "this walk reports is mine, not the product's.");
        return `Offered “${nowT}” and “${srvT}”. Sheet: "${s}". `
             + (MONEY.test(s || "")
                ? `A figure IS shown here (${(s.match(MONEY)||[])[0]}) — so the app does tell me `
                + `what I owe before I choose. My finding would be wrong.`
                : `Still not one money figure. I have been told WHO I am paying for and asked `
                + `HOW I want to pay, and never once told WHAT I owe.`);
      });

    await jr.soft("DINER 1", "I get cold feet and back out without paying", async id => {
      const back = p1.getByRole("button", { name: /back/i }).first();
      if (!(await back.count())) throw new Error("No Back control on the pay-how screen.");
      await back.evaluate(e => e.click());
      await p1.waitForTimeout(DWELL + 2000);
      const t = await look(p1, jr, "diner-1", id, "after backing out");
      const ids = await settlementIds(p1);
      return `Back left me on: \`${ids.join("`, `")}\`. Screen: ${t.slice(0, 120)}`;
    });

    await jr.soft("DINER 2", "meanwhile, does my friend's phone know any of this is happening?",
      async id => {
        await p2.reload({ waitUntil: "domcontentloaded" });
        await p2.waitForTimeout(DWELL + 5000);
        const t = await look(p2, jr, "diner-2", id, "her phone while I am at the pay screen");
        const flags = 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/i.test(x)))].sort());
        return flags.length
          ? `Her phone shows: \`${flags.join("`, `")}\``
          : `Nothing. Her phone shows the ordinary menu — no notice that someone is settling `
          + `the whole table's bill on her behalf. Screen: ${t.slice(0, 120)}`;
      });

    verdict = "WALKED";
    why = `Drove two diners from sitting down to the pay-how fork and stopped at the payment `
        + `boundary by choice. The settle path presented itself as: ${forkShape}.`;
  } 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} has a live session with two guests, one `
          + `placed order, an open check request and an un-completed cover intent. Nothing was `
          + `paid, no card was entered. Table 9971 from pass 1 is in the same state.`);
    jr.finish(verdict, why);
    await closeRole(c1, jr, "diner-1");
    await closeRole(c2, jr, "diner-2");
  }
});
