/* WITNESS · mosaic_v2-place-order-chaos (W-060) — QRATE-UAT-1
 *
 * Ten steps. Two are raw-websocket frame-shape assertions with no screen. Three are an OWNER
 * turning fault injection on and off (http_500, drop_ack). Four are the diner watching an order
 * fail, then succeed, then survive a lost acknowledgement. One is cleanup.
 *
 * The question I can actually answer as a customer: can I place an order at this restaurant at
 * all right now, and is there anywhere an owner could turn this fault injection on or off?
 * Note that the second question matters on its own: if a canary run leaves chaos switched ON,
 * a real diner at this restaurant cannot order - so I look at what happens to ME first.
 *
 * CLEANUP: I cancel my own dish at the end.
 */
import { test } from "@playwright/test";
import { journal, look, roleContext, deskContext, closeRole, signInOwner, account,
         arriveAsDiner, PATRON, OWNER, 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["mosaic_v2-place-order-chaos"];
const TABLE = 9953;
const OUT = "/workspace/uat/wave1/session-recon";

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("mosaic_v2-place-order-chaos", async ({ browser }) => {
  const jr = journal("mosaic_v2-place-order-chaos");
  jr.target({ app: "patron+owner", patronUrl: PATRON, ownerUrl: OWNER, restaurantId: RID,
              table: TABLE, fixture: "Canary Mosaic V2 Place Order Chaos — dev" });
  jr.note("_Half this journey is fault injection an owner turns on and off. So the two things "
        + "I can honestly check are: does ordering work for a real customer here right now, and "
        + "is there anywhere in the product an owner could reach that switch?_");

  const pc = await roleContext(browser, jr, "patron");
  const p = await pc.newPage();
  const oc = await deskContext(browser, jr, "owner");
  const o = await oc.newPage();
  const rec = {};
  let verdict = "INCOMPLETE", why = "", placed = false;
  /* Watch what the server says when I press order — a diner sees only the screen, but if the
     screen lies about a failure this is how I would know which one to believe. */
  const net = [];
  p.on("response", r => { if (/order/i.test(r.url())) net.push({ s: r.status(), u: r.url().replace(/^https?:\/\/[^/]+/, "").slice(0, 90) }); });

  try {
    await jr.step("PATRON", "I sit down at this restaurant and find something to order", 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) })));
      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 here is empty: ${JSON.stringify(rec.byCourse)}. Nothing to order.`);
      return `${n} dishes once I open the fullest course. Per course: ${JSON.stringify(rec.byCourse)}. Screen: ${t.slice(0, 120)}`;
    });

    await jr.step("PATRON", "I add a dish", 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 it either goes to the kitchen or it does not", 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, 10), screen: t.slice(0, 300),
                    errorish: /fail|sorry|again|couldn|error|problem/i.test(t) };
      jr.note(`> **What the server answered when I pressed order:** ${net.map(n => `${n.s} ${n.u}`).join(" · ") || "no order request seen from the page"}`);
      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 `I pressed "${label}" and it worked: the Ordered tab reads "${led.sheet.slice(0, 160)}", badge "${led.badge}". `
           + `So this restaurant is NOT sitting in a failing state right now.`;
    });

    await jr.step("OWNER", "I go looking for the switch that makes ordering fail", async id => {
      const a = account("canaryOwner");
      await signInOwner(o, a.email, a.password, RID);
      await o.goto(`${OWNER}/owner/dashboard/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
      await o.waitForTimeout(DWELL);
      const nav = await o.evaluate(() => [...new Set([...document.querySelectorAll("nav a[href], aside a[href], a[data-testid^=nav-]")]
        .filter(e => e.offsetParent !== null).map(e => e.getAttribute("href")).filter(Boolean))]);
      const labels = await o.evaluate(() => [...new Set([...document.querySelectorAll("nav a[href], aside a[href]")]
        .filter(e => e.offsetParent !== null).map(e => (e.innerText || "").replace(/\s+/g, " ").trim()).filter(Boolean))]);
      jr.note(`> **This restaurant's sidebar:** ${labels.join(" · ")}`);
      rec.chaosSearch = [];
      for (const href of nav.concat([`/owner/settings/pos/?restaurantId=${RID}`, `/owner/settings/payments/?restaurantId=${RID}`])) {
        await o.goto(new URL(href, OWNER).href, { waitUntil: "domcontentloaded" }).catch(() => {});
        await o.waitForTimeout(3500);
        const hit = await o.evaluate(() => {
          const txt = (document.querySelector("main") || document.body).innerText;
          const ctl = [...document.querySelectorAll("button,[role=switch],input,select")]
            .filter(e => e.offsetParent !== null)
            .map(e => `${e.getAttribute("data-testid") || ""} ${(e.innerText || "").trim()}`).join(" ");
          return (`${txt} ${ctl}`.match(/chaos|fault|inject|http_?500|drop_?ack|silent_?hang|simulat/gi) || []);
        });
        rec.chaosSearch.push({ href: href.split("?")[0], hits: [...new Set(hit)] });
      }
      await look(o, jr, "owner", id, "last owner screen searched for a chaos switch");
      fs.writeFileSync(`${OUT}/w060-chaos.json`, JSON.stringify(rec.chaosSearch, null, 1));
      jr.note("> **Every owner screen searched for fault injection:**\n>\n> - "
            + rec.chaosSearch.map(s => `\`${s.href}\` → ${s.hits.length ? s.hits.join(", ") : "nothing"}`).join("\n> - "));
      const any = rec.chaosSearch.some(s => s.hits.length);
      if (!any)
        throw new Error(`I walked all ${rec.chaosSearch.length} owner screens for this restaurant and none of `
          + "them mentions chaos, fault injection, http_500 or drop_ack. The three OWNER steps of this journey "
          + "are not things an owner can do in the product — they are a test hook.");
      return `Found: ${rec.chaosSearch.filter(s => s.hits.length).map(s => `${s.href} → ${s.hits.join(",")}`).join(" ; ")}`;
    });

    verdict = "WALKED"; why = "";
    jr.finish(verdict, "walked what has a screen");
  } catch (e) {
    why = String(e.message || e).split("\n")[0];
    jr.finish("STOPPED", why);
  } finally {
    if (placed) {
      await jr.soft("PATRON", "CLEANUP — I cancel my own dish", async id => {
        await p.reload({ waitUntil: "domcontentloaded" });
        await p.waitForTimeout(DWELL + 2000);
        const led = await openLedger(p);
        if (!led.cancels) return `No cancel control offered; table ${TABLE} is left holding one order.`;
        await p.locator('[data-testid^="cancel-item-"]').first().evaluate(el => el.click());
        await p.waitForTimeout(2500);
        const c = p.getByRole("button", { name: /^(remove|cancel item|confirm|yes|cancel order)/i }).first();
        if (await c.count()) { await c.click({ timeout: 12000 }).catch(() => {}); await p.waitForTimeout(DWELL); }
        const after = await openLedger(p);
        await look(p, jr, "patron", id, "after cancelling");
        rec.cleanup = after;
        return `Cancel controls ${led.cancels} → ${after.cancels}; sheet now "${after.sheet.slice(0, 150)}".`;
      });
      jr.finish(verdict === "WALKED" ? "WALKED" : "STOPPED", why + " (cleanup attempted)");
    }
    fs.writeFileSync(`${OUT}/w060.json`, JSON.stringify(rec, null, 1));
    await closeRole(pc, jr, "patron");
    await closeRole(oc, jr, "owner");
  }
});
