/* WITNESS · W-090 wine-order-integrity
 *
 * WHAT THE JOURNEY PROMISES (its own 10-step list): a guest can be walked through the wine
 * helper, pick a wine BY GLASS, change their mind and switch the same wine to BY BOTTLE,
 * end up with exactly ONE cart line at the bottle price, order it, and be charged the price
 * the panel showed with the pour they chose carried through.
 *
 * WHICH RESTAURANT. The canary owner holds a "Canary Wine Sommelier Journey - dev"
 * (7343d667) whose name matches this journey - but I opened all four of its courses as a
 * guest and it sells no wine and shows no CELLAR band at all. The fixture that does carry
 * the wine, the band and the glass/bottle servings is "Canary Mosaic V2 Sommelier - dev"
 * (0c31c6ec), so that is where the promise is observable and that is where I walk it. I do
 * NOT know which of the two the canary itself drives; that is recorded as a caveat, not
 * glossed over.
 *
 * I place a real order, because "charged price === displayed price" is the whole journey and
 * cannot be witnessed without one. I never touch a payment control.
 */
import { test, expect } from "@playwright/test";
import { journal, look, arriveAsDiner, roleContext, closeRole, account,
         PATRON, WAITER, DWELL } from "../lib-witness.mjs";

const RID   = "0c31c6ec-727a-5516-ba8c-77b16a23eea6";   // Canary Mosaic V2 Sommelier — dev
const TABLE = 9946;
const cents = s => { const m = String(s).match(/\$\s?(\d+)(?:\.(\d\d))?/); return m ? Number(m[1])*100 + Number(m[2]||0) : null; };

test("recs-wine-order", async ({ browser }) => {
  const jr = journal("recs-wine-order");
  jr.target({ journey: "W-090 wine-order-integrity", app: "patron (mosaic_v2) + waiter",
              baseUrl: PATRON, restaurant: "Canary Mosaic V2 Sommelier — dev",
              restaurantId: RID, table: TABLE });
  jr.note("**Fixture caveat.** The restaurant NAMED for this journey (Canary Wine Sommelier "
        + "Journey — dev, 7343d667) sells no wine and shows no CELLAR band on any of its four "
        + "courses today. I walk the fixture where the wine actually is — Canary Mosaic V2 "
        + "Sommelier — dev — and cannot tell from outside which one the canary drives.");

  const ctx = await roleContext(browser, jr, "patron");
  const p = await ctx.newPage();
  let verdict = "INCOMPLETE", why = "", glassC = null, bottleC = null, wineName = "", orderSeen = false;
  const orderPosts = [];
  p.on("response", r => { if (/orders/.test(r.url()) && r.request().method()==="POST") orderPosts.push(`${r.status()} ${r.url().split("?")[0].split("/").slice(-2).join("/")}`); });

  const pickPanel = () => p.locator('[data-testid="m2-sommelier-pick"]').first();
  const readPanel = async () => (await p.locator('[data-testid="m2-sommelier-result"], [data-testid="m2-sommelier-pick"]').first().innerText().catch(()=> "")).replace(/\s+/g," ");
  const tap = async (loc, what) => {
    await loc.click({ timeout: 20000 }).catch(async () => { await loc.evaluate(e => e.click()); });
    await p.waitForTimeout(3500);
    return what;
  };
  const answerQuiz = async () => {
    const picked = [];
    for (const q of [1,2,3]) {
      const opts = p.locator(`[data-testid="m2-sommelier-q-${q}"] [data-testid^="m2-sommelier-option-"]`);
      if (!(await opts.count())) { picked.push(`q${q}: nothing offered`); continue; }
      const b = opts.first();
      const tid = await b.getAttribute("data-testid");
      await tap(b, tid);
      picked.push(tid.replace("m2-sommelier-option-",""));
    }
    await p.waitForTimeout(4000);
    return picked;
  };
  const openCellar = async () => {
    await tap(p.locator('[data-testid="m2-sommelier-banner"]').first(), "band");
    await p.waitForTimeout(2000);
  };
  const closeCellar = async () => {
    const x = p.locator('[data-testid="m2-sommelier-close"]').first();
    if (await x.count()) await tap(x, "close");
  };
  const openCart = async () => {
    const t = p.locator('[data-testid="m2-view-order"], [data-testid="m2-dock-cart"], [data-testid="m2-dock-trigger"]').first();
    await t.evaluate(el => el.click()).catch(()=>{});
    await p.waitForTimeout(DWELL);
    return p.locator('[data-testid="cart-sheet"]').first();
  };
  const closeCart = async () => { await p.keyboard.press("Escape").catch(()=>{}); await p.waitForTimeout(2500); };

  try {
    await jr.step("PATRON", "I sit down and the restaurant offers to help me pick a wine", async id => {
      const g = await arriveAsDiner(p, RID, TABLE, "mosaic_v2");
      const band = p.locator('[data-testid="m2-sommelier-banner"]').first();
      await expect(band, "THE CELLAR band should be on the menu").toBeVisible({ timeout: 25000 });
      const bt = (await band.innerText()).replace(/\s+/g," ");
      await look(p, jr, "patron", id, "menu with the cellar band");
      return `passed ${g.join(", ")||"no gates"}. The band reads: "${bt}"`;
    });

    await jr.step("PATRON", "I answer its three questions and it names a wine, with prices", async id => {
      await openCellar();
      const before = await p.locator('[data-testid="m2-sommelier-empty"]').count();
      const picked = await answerQuiz();
      const t = await look(p, jr, "patron", id, "the wine it picked for me");
      const after = await p.locator('[data-testid="m2-sommelier-empty"]').count();
      const txt = await readPanel();
      jr.note(`> **I answered:** ${picked.join(" · ")}`);
      jr.note(`> **The pick panel reads:** ${txt.slice(0,420)}`);
      // Success must show its OWN evidence: the pre-answer placeholder gone AND a priced wine named.
      expect(after, "the pre-answer placeholder should be gone once I have answered").toBe(0);
      expect(txt, "it should name a priced wine").toMatch(/\$\d/);
      wineName = (txt.match(/Canary [A-Z][a-z]+ Wine/) || [""])[0];
      return `placeholder ${before}->${after}. It recommends: ${txt.slice(0,260)}`;
    });

    await jr.step("PATRON", "It quotes me a BY GLASS price and I add that glass", async id => {
      const g = pickPanel().locator('[data-testid="m2-sommelier-pick-add-glass"]').first();
      const glassTxt = (await g.innerText()).replace(/\s+/g," ");
      glassC = cents(glassTxt);
      await tap(g, "BY GLASS");
      const cta = p.locator('[data-testid="m2-sommelier-add-to-order"]').first();
      const ctaTxt = (await cta.innerText().catch(()=> "")).replace(/\s+/g," ");
      await look(p, jr, "patron", id, "by the glass, before adding");
      await tap(cta, "add to order");
      await look(p, jr, "patron", id + "b", "just after adding the glass");
      jr.note(`> **BY GLASS reads** "${glassTxt}" (${glassC}c) · **the add button reads** "${ctaTxt}"`);
      expect(glassC, "the glass price should be readable on the panel").toBeGreaterThan(0);
      return `panel BY GLASS "${glassTxt}" = ${glassC}c; the add button said "${ctaTxt}"`;
    });

    await jr.step("PATRON", "My cart shows that glass at exactly the price I was quoted", async id => {
      await closeCellar();
      const sheet = await openCart();
      await expect(sheet, "the cart should open").toBeVisible({ timeout: 20000 });
      const stxt = (await sheet.innerText()).replace(/\s+/g," ");
      const lines = await sheet.locator('[data-testid^="cart-item-"]').count();
      await look(p, jr, "patron", id, "cart with the glass");
      jr.note(`> **Cart (read only inside the cart sheet):** ${stxt.slice(0,320)}`);
      const lineC = cents((stxt.match(/\$\d+\.\d\d/)||[""])[0]);
      expect(lineC, `the cart line should cost what the panel quoted (${glassC}c)`).toBe(glassC);
      await closeCart();
      return `${lines} cart line(s); the line reads ${lineC}c and the panel quoted ${glassC}c`;
    });

    await jr.step("PATRON", "I change my mind: same wine, BY BOTTLE instead", async id => {
      await openCellar();
      const retake = p.locator('[data-testid="m2-sommelier-retake"]').first();
      if (await retake.count()) { await tap(retake, "start over"); }
      const again = await answerQuiz();
      const b = pickPanel().locator('[data-testid="m2-sommelier-pick-add-bottle"]').first();
      const bTxt = (await b.innerText()).replace(/\s+/g," ");
      bottleC = cents(bTxt);
      await tap(b, "BY BOTTLE");
      const cta = p.locator('[data-testid="m2-sommelier-add-to-order"]').first();
      const ctaTxt = (await cta.innerText().catch(()=> "")).replace(/\s+/g," ");
      await look(p, jr, "patron", id, "by the bottle, before adding");
      await tap(cta, "add to order");
      jr.note(`> **I answered again:** ${again.join(" · ")}`);
      jr.note(`> **BY BOTTLE reads** "${bTxt}" (${bottleC}c) · **the add button reads** "${ctaTxt}"`);
      expect(bottleC, "the bottle price should be readable and dearer than the glass").toBeGreaterThan(glassC);
      return `panel BY BOTTLE "${bTxt}" = ${bottleC}c; the add button said "${ctaTxt}"`;
    });

    await jr.step("PATRON", "My cart holds ONE line — the bottle replaced the glass, it did not add a second",
      async id => {
        await closeCellar();
        const sheet = await openCart();
        const lines = sheet.locator('[data-testid^="cart-item-"]');
        await expect.poll(async () => lines.count(), { timeout: 20000 }).toBe(1);
        await p.waitForTimeout(750);                       // poll matches the instant it hits 1
        const settled = await lines.count();               // ...so re-read after it settles
        const stxt = (await sheet.innerText()).replace(/\s+/g," ");
        await look(p, jr, "patron", id, "cart after switching to the bottle");
        jr.note(`> **Cart after the switch:** ${stxt.slice(0,320)}`);
        expect(settled, "still exactly one line once everything has settled").toBe(1);
        expect(cents((stxt.match(/\$\d+\.\d\d/)||[""])[0]),
               `the one line should be the bottle price (${bottleC}c)`).toBe(bottleC);
        return `settled line count = ${settled}; it reads the bottle price ${bottleC}c. Cart: ${stxt.slice(0,200)}`;
      });

    await jr.step("PATRON", "I order it, and I am charged the price I was shown", async id => {
      const btn = p.locator('[data-testid="order-now-btn"]').first();
      const btnTxt = (await btn.innerText().catch(()=> "")).replace(/\s+/g," ");
      await btn.click({ timeout: 25000 }).catch(async()=>{ await btn.evaluate(e=>e.click()); });
      await p.waitForTimeout(DWELL + 6000);
      await look(p, jr, "patron", id, "just after ordering");
      const sheet = await openCart();
      const ordered = sheet.getByText(/^Ordered$/i).first();
      if (await ordered.count()) { await ordered.click({ timeout: 15000 }).catch(async()=>{ await ordered.evaluate(e=>e.click()); }); await p.waitForTimeout(DWELL); }
      const stxt = (await sheet.innerText()).replace(/\s+/g," ");
      await look(p, jr, "patron", id, "the Ordered ledger");
      orderSeen = /ordered/i.test(stxt);
      jr.note(`> **POST /diner/orders answered:** ${orderPosts.join(", ")||"no order request seen"}`);
      jr.note(`> **The Ordered ledger reads:** ${stxt.slice(0,320)}`);
      // The success state must show its OWN evidence: the wine moved out of "The Cart" and into
      // the Ordered ledger, still at the bottle price. (The network POST is reported for colour;
      // it is not the oracle - a diner cannot see it.)
      expect(stxt, "the ledger should show the wine as ORDERED").toMatch(/ORDERED/i);
      expect(cents((stxt.match(/\$\d+\.\d\d/)||[""])[0]),
             `the ordered line should still be the bottle price (${bottleC}c)`).toBe(bottleC);
      return `order button said "${btnTxt}"; server answered ${orderPosts.join(",")}; the Ordered `
           + `ledger shows ${bottleC}c. ${stxt.slice(0,200)}`;
    });
    await closeRole(ctx, jr, "patron");

    // ---- the staff half -----------------------------------------------------------------
    const wctx = await roleContext(browser, jr, "waiter", "Desktop Chrome");
    const w = await wctx.newPage();
    try {
      await jr.soft("WAITER", "Staff can see my order and which pour I chose", async id => {
        const a = account("canaryOwner");
        await w.goto(`${WAITER}/owner/staff-orders/`, { waitUntil: "domcontentloaded" });
        await w.waitForTimeout(DWELL);
        const em = w.locator('[data-testid="login-email"], input[type=email]').first();
        if (await em.count()) {
          await em.fill(a.email);
          await w.locator('[data-testid="login-password"], input[type=password]').first().fill(a.password);
          await w.locator('[data-testid="login-submit"]').first().click({ timeout: 25000 }).catch(()=>{});
          await w.waitForTimeout(DWELL + 8000);
        }
        const t = await look(w, jr, "waiter", id, "waiter app");
        if (!new RegExp(`${TABLE}`).test(t))
          throw new Error(`the only staff login this container holds is the restaurant OWNER, and `
                        + `the waiter app tells them "${(t.match(/No tables[^.]*/i)||["<no table list>"])[0]}" `
                        + `— so table ${TABLE}'s order, and the pour on it, are not visible to me. `
                        + `Screen: ${t.slice(0,200)}`);
        return `table ${TABLE} is on the waiter board: ${t.slice(0,260)}`;
      });
    } finally { await closeRole(wctx, jr, "waiter"); }

    verdict = "WALKED";
    why = `Walked the whole diner half: cellar band -> quiz -> pick -> by the glass (${glassC}c) `
        + `-> switched the same wine to by the bottle (${bottleC}c) -> exactly one cart line -> `
        + `ordered -> the Ordered ledger shows the bottle price. The staff view of the pour is `
        + `the one thing I could not reach.`;
  } catch (e) {
    verdict = "STOPPED"; why = String(e.message||e).split("\n")[0].slice(0,320);
  } finally {
    jr.note(orderPosts.length
      ? `**Cleanup:** I placed one real order on table ${TABLE} of Canary Mosaic V2 Sommelier — dev `
        + `(server answered ${orderPosts.join(", ")}). I cannot settle or close it — no staff account `
        + `in this container is assigned to that table. 99xx is a test table number, not one a real `
        + `diner uses. Nothing was paid.`
      : `**Cleanup:** none — I never got as far as placing an order, so nothing was left behind but `
        + `an open guest session on test table ${TABLE}.`);
    jr.finish(verdict, why);
    try { await closeRole(ctx, jr, "patron"); } catch {}
  }
});
