/* WITNESS · mosaic_v2-split   (definitive walk, clean table, exactly two diners)
   The promise, as a person would put it: two of us eat at one table, and when it comes to
   paying we should not have to argue — the app should let us split the bill, and both of
   our phones should agree about what was ordered and what is being split.

   Nobody has walked this. The board says PASSED. The earlier recon lane stopped at "place
   the order" because it was not allowed to order; I am, so I go past that wall.

   I PRESS NOTHING THAT PAYS. These fixtures sit on a live Stripe merchant on dev, so I open
   the split flow, read it, cancel out of it, and stop.                                      */
import { test, expect } from "@playwright/test";
import { journal, look, DWELL, roleContext, closeRole, PATRON } from "../lib-witness.mjs";
import { arrive, tap, whoIsHere, settleControls, openSheet } from "./split-lib.mjs";

const RID = "91b67f1d-ef32-52ed-a5ad-b2532fb914a3";   // canary mosaic_v2 split fixture
const TABLE = 9943;   // NOT a table the canary drives (it uses 9981, 9991, 10220, 10224, 10225)

/** Read the order sheet itself, not the whole page — the menu shows through underneath it. */
const sheetText = async page =>
  (await page.locator('[data-testid="cart-sheet"]').innerText().catch(() => "")).replace(/\s+/g, " ").trim();

/** Open the sheet and switch to the "Ordered" ledger tab. */
async function openOrderedTab(page) {
  await openSheet(page);
  const tab = page.locator('[data-testid="m2-cart-tab-table"]').first();
  if (await tab.count()) await tap(page, tab);
  await page.waitForTimeout(DWELL);
}

test("mosaic_v2-split", async ({ browser }) => {
  const jr = journal("mosaic_v2-split");
  jr.target({ app: "patron (mosaic_v2)", baseUrl: PATRON, restaurantId: RID, table: TABLE });
  jr.note(`**Where:** the split canary fixture, table ${TABLE} — a table the canary itself does not drive.`);
  jr.note("**Who:** exactly two of us, A and B, on two separate phones. Not one person in two tabs.");
  jr.note("**I press nothing that pays.** I open the split flow, read it, and cancel out of it.");

  const cA = await roleContext(browser, jr, "patron-a");
  const cB = await roleContext(browser, jr, "patron-b");
  const a = await cA.newPage(), b = await cB.newPage();
  let verdict = "INCOMPLETE", why = "", leftBehind = "nothing";

  try {
    await jr.step("PATRON A", `I sit down at table ${TABLE} and the menu opens`, async id => {
      const g = await arrive(a, RID, TABLE);
      const t = await look(a, jr, "patron-a", id, "a arrives");
      const here = await whoIsHere(a);
      jr.note("> **Who was already at this table when I sat down:** "
            + (here.length ? here.map(c => `\`${c.text.replace(/\n/g, " ")}\``).join(" · ")
                           : "nobody — the table was empty"));
      expect(here.length, `table ${TABLE} should be empty before I start; if it is not, my count `
                        + "claims later would be about strangers").toBe(0);
      return `passed: ${g.join(", ") || "no gates"}. Table empty. ${t.slice(0, 110)}`;
    });

    await jr.step("PATRON B", "My friend sits down at the same table", async id => {
      const g = await arrive(b, RID, TABLE);
      const t = await look(b, jr, "patron-b", id, "b arrives");
      return `passed: ${g.join(", ") || "no gates"}. ${t.slice(0, 110)}`;
    });

    await jr.step("PATRON A", "I order one drink — exactly one", async id => {
      const add = a.locator('[data-testid^="m2-add-"]').first();
      await expect(add, "something should be orderable").toBeVisible({ timeout: 25000 });
      const name = (await add.getAttribute("aria-label")) || "(unnamed)";
      await tap(a, add);
      const readCount = async () => {
        const c = a.locator('[data-testid="m2-dock-cart-count"], [data-testid="m2-view-order-count"]').first();
        return (await c.count()) ? Number((await c.innerText()).replace(/\D/g, "")) : null;
      };
      await expect.poll(readCount, { timeout: 20000 }).toBe(1);
      await a.waitForTimeout(900);                     // let any late second write land
      expect(await readCount(), "one tap must add exactly one thing").toBe(1);
      await look(a, jr, "patron-a", id, "a added one");
      return `added "${name}"; my cart settled on exactly 1`;
    });

    await jr.step("PATRON B", "My friend orders one thing for themselves too", async id => {
      const add = b.locator('[data-testid^="m2-add-"]').first();
      const name = (await add.getAttribute("aria-label")) || "(unnamed)";
      await tap(b, add);
      await b.waitForTimeout(DWELL);
      await look(b, jr, "patron-b", id, "b added one");
      return `they added "${name}"`;
    });

    await jr.step("PATRON A", "My cart shows both of us — my things and my friend's", async id => {
      await openSheet(a);
      const t = await look(a, jr, "patron-a", id, "a shared cart");
      const here = await whoIsHere(a);
      const all = here.find(c => c.testid === "cart-scope-chip-all");
      const guests = here.filter(c => c.testid !== "cart-scope-chip-all");
      jr.note("> **My cart says the table holds:** "
            + here.map(c => `\`${c.text.replace(/\n/g, " ")}\``).join(" · "));
      jr.note(`> **The running total my cart shows me is** \`${(await sheetText(a)).match(/TOTAL \$[\d.]+/i)?.[0] || "(none)"}\``);
      expect(guests.length, "there should be exactly two of us at this table").toBe(2);
      expect(all?.text.replace(/\D/g, ""), "the 'All' chip should count both our items").toBe("2");
      return `two guests, 'All' reads "${all.text.replace(/\n/g, " ")}"; sheet: ${(await sheetText(a)).slice(0, 160)}`;
    });

    await jr.step("PATRON A", "I press Place order", async id => {
      const btn = a.locator('[data-testid="order-now-btn"]').first();
      await expect(btn, "there should be a way to send the order").toBeVisible({ timeout: 20000 });
      const label = (await btn.innerText()).replace(/\s+/g, " ").trim();
      jr.note(`> **The button I pressed says** \`${label}\` — note whether that price covers one of us or both.`);
      await tap(a, btn, 25000);
      await a.waitForTimeout(DWELL + 5000);
      leftBehind = `a table session and a placed order on table ${TABLE}`;
      await look(a, jr, "patron-a", id, "a ordered");
      return `pressed "${label}"`;
    });

    await jr.step("PATRON A", "My own Ordered list shows what went to the kitchen", async id => {
      await openOrderedTab(a);
      await look(a, jr, "patron-a", id, "a ordered tab");
      const s = await sheetText(a);
      jr.note(`> **MY Ordered list:** ${s.slice(0, 400) || "(no text)"}`);
      expect(s, "my Ordered list must not still say nothing was sent").not.toMatch(/nothing sent yet/i);
      return s.slice(0, 240);
    });

    await jr.step("PATRON B", "My friend, sitting right there, looks at the Ordered list on their own phone",
      async id => {
        await openOrderedTab(b);
        await look(b, jr, "patron-b", id, "b ordered tab before reload");
        const s = await sheetText(b);
        const stale = /nothing sent yet/i.test(s);
        jr.note(`> **MY FRIEND'S Ordered list, without touching anything:** ${s.slice(0, 400) || "(no text)"}`);
        jr.note(stale
          ? "> ⚠️ Their phone says nothing has been sent, although I ordered at this table moments ago."
          : "> Their phone shows the order.");
        return stale ? "says 'Nothing sent yet'" : s.slice(0, 240);
      });

    await jr.step("PATRON B", "They give it a moment and refresh — the way a person would if a screen looked wrong",
      async id => {
        await b.reload({ waitUntil: "domcontentloaded" });
        await b.waitForTimeout(DWELL + 6000);
        await openOrderedTab(b);
        await look(b, jr, "patron-b", id, "b ordered tab after reload");
        const s = await sheetText(b);
        const stale = /nothing sent yet/i.test(s);
        jr.note(`> **MY FRIEND'S Ordered list after a refresh:** ${s.slice(0, 400) || "(no text)"}`);
        jr.note(stale
          ? "> ⚠️ **Still nothing.** A refresh does not fix it, so this is not a screen that failed to "
            + "update — the second person at the table is simply not shown the table's order."
          : "> The refresh brought the order in — so the first screen was stale, not blind.");
        return stale ? "STILL says 'Nothing sent yet' after a full refresh" : s.slice(0, 240);
      });

    await jr.step("PATRON A", "I ask to settle up, and write down exactly what I am offered", async id => {
      for (const sel of ['[data-testid="layout-service-button"]', '[data-testid="cart-service-btn"]']) {
        const s = a.locator(sel).first();
        if (await s.count()) { await tap(a, s); break; }
      }
      await a.waitForTimeout(DWELL);
      await look(a, jr, "patron-a", id + "a", "a service drawer");
      const bill = a.locator('[data-testid="service-bubble-ready_for_bill"]').first();
      if (!(await bill.count())) throw new Error("no 'Check Please' option in the service drawer");
      await tap(a, bill);
      await a.waitForTimeout(DWELL + 5000);
      await look(a, jr, "patron-a", id, "a asked to settle");
      const ctrls = await settleControls(a);
      const bubble = (await a.locator('[data-testid="service-bubble"]').innerText().catch(() => "")).replace(/\s+/g, " ").trim();
      jr.note("> **What I am offered once I ask to settle:** "
            + (ctrls.length ? "`" + ctrls.join("`, `") + "`" : "**nothing**"));
      jr.note(`> **In the words on my screen:** ${bubble.slice(0, 320) || "(the settle panel had no text)"}`);
      expect(ctrls, "asking to settle should offer me a way to split").toContain("settlement-split");
      return `offered: ${ctrls.join(", ")} — panel reads: ${bubble.slice(0, 200)}`;
    });

    await jr.step("PATRON A", "I choose to split the check and read what it proposes", async id => {
      const split = a.locator('[data-testid="settlement-split"]').first();
      const label = (await split.innerText()).replace(/\s+/g, " ").trim();
      await tap(a, split);
      await a.waitForTimeout(DWELL);
      await look(a, jr, "patron-a", id, "a split step");
      const step = (await a.locator('[data-testid="split-check-step"]').innerText().catch(() => "")).replace(/\s+/g, " ").trim();
      const n = await a.locator('[data-testid="split-count-input"]').inputValue().catch(async () =>
        (await a.locator('[data-testid="split-count-input"]').innerText().catch(() => "?")).trim());
      jr.note(`> **The button said** \`${label}\`**, and the panel it opened says:** ${step.slice(0, 400) || "(no text)"}`);
      jr.note(`> **It proposes splitting** \`${n}\` **ways.** There are two of us at this table.`);
      jr.note("> **What it does NOT offer:** any way to say which dish is whose. The only controls in "
            + "this panel are a headcount stepper (`split-decrement` / `split-increment`), `split-confirm` "
            + "and `split-cancel` — so this splits the bill evenly by head, not by what each of us ate.");
      return `"${label}" → proposes ${n} ways; panel: ${step.slice(0, 220)}`;
    });

    await jr.soft("PATRON A", "I try to set the split to just the two of us", async id => {
      const dec = a.locator('[data-testid="split-decrement"]').first();
      const read = async () => a.locator('[data-testid="split-count-input"]').inputValue()
        .catch(async () => (await a.locator('[data-testid="split-count-input"]').innerText()).trim());
      const before = await read();
      for (let i = 0; i < 6 && Number(await read()) > 2; i++) { await tap(a, dec, 8000); }
      const after = await read();
      await look(a, jr, "patron-a", id, "a split count adjusted");
      jr.note(`> **Headcount went from \`${before}\` to \`${after}\` using the stepper.**`);
      return `${before} → ${after}`;
    });

    await jr.step("PATRON A", "I back out without paying", async id => {
      const cancel = a.locator('[data-testid="split-cancel"]').first();
      await expect(cancel, "there must be a way out of the split panel without paying").toBeVisible({ timeout: 15000 });
      await tap(a, cancel);
      await a.waitForTimeout(DWELL);
      await look(a, jr, "patron-a", id, "a cancelled split");
      const still = await a.locator('[data-testid="split-check-step"]').count();
      jr.note(`> **After Cancel the split panel is ${still ? "STILL on screen" : "gone"}.**`);
      expect(still, "cancelling should close the split panel").toBe(0);
      return "backed out of the split without paying";
    });

    await jr.soft("PATRON B", "My friend can see that I am settling the table's bill", async id => {
      await b.reload({ waitUntil: "domcontentloaded" });
      await b.waitForTimeout(DWELL + 6000);
      await openOrderedTab(b);
      await look(b, jr, "patron-b", id, "b after split attempt");
      const s = await sheetText(b);
      const ctrls = await settleControls(b);
      jr.note(`> **My friend's screen while I was settling:** ${s.slice(0, 300) || "(no sheet text)"}`);
      jr.note("> **Their settle controls:** " + (ctrls.length ? "`" + ctrls.join("`, `") + "`" : "none — "
            + "nothing on their phone mentions a bill, a split, or that I asked to settle"));
      return `${ctrls.length} settle control(s); sheet: ${s.slice(0, 200)}`;
    });

    verdict = "WALKED";
    why = "drove two real diners from sitting down through ordering to opening the split panel, and "
        + "backed out without paying";
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message || e).split("\n")[0].slice(0, 300);
  } finally {
    jr.note(`> **What I left behind:** ${leftBehind}. The staff app lists no table cards for this `
          + "fixture, so I had no waiter's way to cancel the items or close the table.");
    jr.finish(verdict, why);
    await closeRole(cA, jr, "patron-a");
    await closeRole(cB, jr, "patron-b");
  }
});
