/* WITNESS · pos-order-push (W-085) — QRATE-UAT-1
 *
 * WHY THIS EXISTS: the ledger row was coded "no screen — every step is a direct API call",
 * while its OWN finding said a real guest placed an order "through the real cart UI". Both
 * cannot be true. The finding is the more specific claim, so the row was recoded and now
 * owes what it never had: a filmed walk. This is that walk.
 *
 * The four steps are: a diner places an order, then the restaurant's till receives it,
 * acknowledges it, and reconciles. Only the FIRST has a screen a customer can see. The till
 * side is a system this container cannot observe, and saying so is the honest result.
 *
 * FIXTURE NOTE: the prior walk recorded "canary-kitchen (POS push)", which names two
 * different restaurants in the fixture map. This drives the one the JOURNEY is named for
 * (pos-push) and reports what is actually there, rather than repeating the ambiguity.
 *
 * CLEANUP: I cancel my own dish at the end. Tables here are already cluttered.
 */
import { test } from "@playwright/test";
import { journal, look, roleContext, closeRole, arriveAsDiner, PATRON, DWELL } from "../lib-witness.mjs";
import fs from "node:fs";

const F = JSON.parse(fs.readFileSync("/workspace/uat/runs/wave0/fixtures.json", "utf8"));
const RID = F["pos-push"];
const TABLE = 9951;

const tap = async (page, sel) => {
  const l = page.locator(sel).first();
  if (!(await l.count())) return false;
  await l.click({ timeout: 10000 }).catch(async () => { await l.evaluate(el => el.click()); });
  return true;
};
async function openLedger(page) {
  await tap(page, '[data-testid="m2-dock-trigger"]'); await page.waitForTimeout(1800);
  await tap(page, '[data-testid="m2-dock-cart"]');    await page.waitForTimeout(DWELL);
  await tap(page, '[data-testid="m2-cart-tab-table"]'); await page.waitForTimeout(3000);
  return {
    cancels: await page.locator('[data-testid^="cancel-item-"]').count(),
    badge: (await page.locator('[data-testid="order-status-badge"]').first().innerText().catch(() => "")).replace(/\s+/g, " ").trim(),
    sheet: (await page.locator('[data-testid="cart-sheet"]').first().innerText().catch(() => "")).replace(/\s+/g, " ").trim().slice(0, 300),
  };
}

test("pos-order-push", async ({ browser }) => {
  const jr = journal("pos-order-push");
  jr.target({ app: "patron", patronUrl: PATRON, restaurantId: RID, table: TABLE,
              fixture: "Canary POS Push — dev" });
  jr.note("_Only the first of these four steps has a screen. A diner places an order; what a "
        + "restaurant's till then does with it is not something a customer can see, and not "
        + "something this container can reach. I check the half that is visible and say so._");

  const pc = await roleContext(browser, jr, "patron");
  const p = await pc.newPage();
  const rec = {};
  let verdict = "INCOMPLETE", why = "", placed = false;
  const net = [];
  p.on("response", r => { if (/order/i.test(r.url())) net.push({ s: r.status(), u: r.url().replace(/^https?:\/\/[^/]+/, "").slice(0, 80) }); });

  try {
    await jr.step("PATRON", "I sit down at this restaurant and see what is on the menu", async id => {
      await arriveAsDiner(p, RID, TABLE);
      const t = await look(p, jr, "patron", id, "arrived");
      const secs = await p.evaluate(() => [...document.querySelectorAll('[data-testid^="m2-section-"]')]
        .map(e => ({ tid: e.getAttribute("data-testid"), label: (e.innerText || "").replace(/\s+/g, " ").trim().slice(0, 20) })));
      // mosaic_v2 opens on BEVERAGES, so anything counted on arrival is drinks. Walk every
      // course and take the fullest, or "the menu is empty" is a claim about one course only.
      rec.byCourse = { "(as it opened)": await p.locator('[data-testid^="m2-tile-"]').count() };
      for (const s of secs) {
        await p.locator(`[data-testid="${s.tid}"]`).first().evaluate(el => el.click()).catch(() => {});
        await p.waitForTimeout(3000);
        rec.byCourse[s.label || s.tid] = await p.locator('[data-testid^="m2-tile-"]').count();
      }
      jr.note(`> **Every course here:** ${JSON.stringify(rec.byCourse)}`);
      const best = Object.entries(rec.byCourse).filter(([k]) => k !== "(as it opened)").sort((a, b) => b[1] - a[1])[0];
      if (best && best[1] > 0) {
        const sec = secs.find(x => (x.label || x.tid) === best[0]);
        await p.locator(`[data-testid="${sec.tid}"]`).first().evaluate(el => el.click()).catch(() => {});
        await p.waitForTimeout(3000);
      }
      const n = await p.locator('[data-testid^="m2-tile-"]').count();
      if (!n) throw new Error(`Every course on this restaurant is empty: ${JSON.stringify(rec.byCourse)}. A diner has nothing to order.`);
      return `${n} dishes in the fullest course. Per course: ${JSON.stringify(rec.byCourse)}.`;
    });

    await jr.step("PATRON", "I add a dish to my order", async id => {
      await p.locator('[data-testid^="m2-tile-"]').first().evaluate(el => el.click());
      await p.waitForTimeout(DWELL);
      await p.locator('[data-testid="composition-add"]').first().click({ timeout: 20000 });
      await p.waitForTimeout(DWELL);
      await look(p, jr, "patron", id, "one in the cart");
      const n = (await p.locator('[data-testid="m2-dock-cart-count"]').first().innerText().catch(() => "")).trim();
      if (!n || n === "0") throw new Error("Nothing landed in the cart.");
      return `Cart pill reads ${n}.`;
    });

    await jr.step("PATRON", "I press Place order and watch whether it reaches the kitchen", async id => {
      await tap(p, '[data-testid="m2-dock-trigger"]'); await p.waitForTimeout(1800);
      await tap(p, '[data-testid="m2-dock-cart"]');    await p.waitForTimeout(DWELL);
      const btn = p.locator('[data-testid="order-now-btn"]').first();
      if (!(await btn.count())) throw new Error("There is no Place order button in my cart.");
      const label = (await btn.innerText()).replace(/\s+/g, " ").trim();
      net.length = 0;
      await tap(p, '[data-testid="order-now-btn"]');
      await p.waitForTimeout(DWELL * 2);
      const t = await look(p, jr, "patron", id, "after pressing order");
      const led = await openLedger(p);
      placed = led.cancels > 0 || /ordered/i.test(led.badge);
      rec.place = { label, led, net: net.slice(0, 8), errorish: /fail|sorry|again|couldn|error|problem/i.test(t) };
      jr.note(`> **What the server answered:** ${net.map(n => `${n.s} ${n.u}`).join(" · ") || "no order request seen"}`);
      if (!placed)
        throw new Error(`My order did not go through. The Ordered tab reads "${led.sheet}" and the screen `
          + `${rec.place.errorish ? "does" : "does not"} tell me anything went wrong.`);
      return `Pressed "${label}". The Ordered tab reads "${led.sheet.slice(0, 160)}", badge "${led.badge}".`;
    });

    await jr.step("PATRON", "I look for any sign the restaurant's till took the order", async id => {
      // The remaining three steps are a till acknowledging and reconciling. A diner has no
      // window onto that, and neither do I. What I CAN do is say precisely what a customer
      // is shown, so the gap is named rather than implied.
      const led = await openLedger(p);
      await look(p, jr, "patron", id, "everything a customer is told after ordering");
      rec.after = led;
      return `All a customer sees is "${led.badge || "(no badge)"}" on their own order. Nothing on `
           + `this screen says whether the restaurant's till received it, acknowledged it, or `
           + `reconciled it - which is what the other three steps of this journey assert.`;
    });

    verdict = "PARTIAL_GREEN";
    why = "The one step with a screen works: a guest can order here and is told it went through. "
        + "The three till-side steps have no customer-facing surface and cannot be seen from here.";
  } catch (e) {
    why = e.message; verdict = placed ? "PARTIAL_GREEN" : "HONEST_RED";
  } finally {
    // CLEANUP — I do not leave my dish standing on a table someone else has to clear.
    try {
      const led = await openLedger(p);
      if (led.cancels > 0) {
        await p.locator('[data-testid^="cancel-item-"]').first().click({ timeout: 8000 });
        await p.waitForTimeout(2500);
        const yes = p.locator('[data-testid="confirm-cancel"], [data-testid="cancel-confirm"]').first();
        if (await yes.count()) { await yes.click().catch(() => {}); await p.waitForTimeout(2500); }
        await look(p, jr, "patron", "cleanup", "after cancelling my own dish");
        jr.note("> **Cleanup:** I cancelled the dish I ordered, so this walk leaves nothing standing.");
      }
    } catch { jr.note("> **Cleanup:** could not cancel my dish - it is still on table " + TABLE + "."); }
    await closeRole(pc, jr, "patron");
    jr.finish(verdict, why);
  }
});
