/* WITNESS · micro-kitchen-order-type-window (W-020) — QRATE-UAT-1
 *
 * What the board claims passed, in a person's words:
 *   1 this really is a configured micro kitchen · 2 a normal order cannot be asked for sooner
 *   than 24h away · 3 a party order cannot be asked for sooner than 48h · 4 the party floor is
 *   strictly later than the normal one · 5 every date offered is real and none is before the
 *   floor · 6 the owner's instructions reach me · 7 nothing is offered past the booking horizon
 *
 * Six of those seven are things a DINER sees on a date-and-time picker. So the walk has two
 * halves: first I look at the owner's side to find out whether these floors can be configured
 * at all and what they are set to, then I go and be the diner and try to reach the picker.
 *
 * SAFETY: read-only on the owner side - I save nothing. I press nothing that pays.
 */
import { test } from "@playwright/test";
import { journal, look, roleContext, deskContext, closeRole, signInOwner, account,
         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["micro-kitchen"];
const TABLE = 10492;
const OUT = "/workspace/uat/wave1/session-recon";

const mainText = page => page.evaluate(() =>
  (document.querySelector("main") || document.body).innerText.replace(/\s+/g, " ").trim());
const tids = page => page.evaluate(() =>
  [...new Set([...document.querySelectorAll("[data-testid]")].map(e => e.getAttribute("data-testid")))].sort());
const fulfil = page => page.evaluate(() =>
  [...document.querySelectorAll("[data-testid]")].map(e => e.getAttribute("data-testid"))
    .filter(t => /pickup|pick-up|delivery|deliver|window|slot|collect|fulfil|schedule|when|time|asap|address|date|party/i.test(t)).sort());
async function answerDietary(page, ms = 15000) {
  const g = page.locator('[data-testid="m2-dietary-gate"]');
  try { await g.first().waitFor({ state: "visible", timeout: ms }); } catch { return false; }
  const no = page.locator('[data-testid="m2-dietary-gate-no"]').first();
  await no.click({ timeout: 10000 }).catch(async () => { await no.evaluate(el => el.click()).catch(() => {}); });
  await g.first().waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
  await page.waitForTimeout(1500); return true;
}
const tap = async (page, sel) => {
  const l = page.locator(sel).first();
  if (!(await l.count())) return false;
  await l.click({ timeout: 8000 }).catch(async () => { await l.evaluate(el => el.click()); });
  return true;
};

test("micro-kitchen-order-type-window", async ({ browser }) => {
  const jr = journal("micro-kitchen-order-type-window");
  jr.target({ app: "owner+patron", ownerUrl: OWNER, patronUrl: PATRON, restaurantId: RID,
              table: TABLE, fixture: "Canary Micro Kitchen — dev (the one WITH collection windows)" });
  jr.note("_Six of this journey's seven steps are things a diner reads off a date-and-time "
        + "picker. So I look at the owner's side first - can these floors even be set? - then "
        + "I go and try to reach that picker as a customer._");
  jr.note("**Read-only on the owner side.** Nothing saved. Nothing paid.");

  const oc = await deskContext(browser, jr, "owner");
  const o = await oc.newPage();
  const pc = await roleContext(browser, jr, "patron");
  const p = await pc.newPage();
  const rec = { owner: {}, patron: {} };
  let verdict = "INCOMPLETE", why = "";

  try {
    await jr.step("OWNER", "I sign in and confirm this really is a micro kitchen", 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 t = await look(o, jr, "owner", id, "owner dashboard for the micro kitchen");
      rec.owner.dashboard = t.slice(0, 900);
      const isMK = /micro kitchen/i.test(t);
      if (!isMK) throw new Error("The owner's own dashboard does not describe this as a micro kitchen.");
      return `The dashboard is headed "${(t.match(/Canary Micro Kitchen[^A-Z]{0,20}/i) || [""])[0]}" and reads: ${t.slice(0, 260)}`;
    });

    await jr.step("OWNER", "I go looking for where the 24h and 48h floors are set", async id => {
      /* I follow the kitchen's OWN navigation rather than typing URLs - a micro kitchen's
         sidebar is not the same as an ordinary restaurant's, and guessing addresses is how
         you report a control missing when you simply looked in the wrong place. */
      const nav = await o.evaluate(() => [...document.querySelectorAll("nav a[href], aside a[href], a[data-testid^=nav-]")]
        .filter(e => e.offsetParent !== null)
        .map(e => ({ label: (e.innerText || "").replace(/\s+/g, " ").trim().slice(0, 30),
                     href: e.getAttribute("href"), tid: e.getAttribute("data-testid") })));
      const seen = new Map();
      for (const n of nav) if (n.href && !seen.has(n.href)) seen.set(n.href, n);
      rec.owner.nav = [...seen.values()];
      jr.note(`> **This kitchen's own sidebar offers me:** ${rec.owner.nav.map(n => `**${n.label}** (\`${n.href}\`)`).join(" · ")}`);

      rec.owner.probes = {};
      for (const n of rec.owner.nav) {
        let st = null;
        try { const r = await o.goto(new URL(n.href, OWNER).href, { waitUntil: "domcontentloaded" }); st = r && r.status(); }
        catch { st = "NAV FAILED"; }
        await o.waitForTimeout(4000);
        const t = await mainText(o);
        rec.owner.probes[n.label || n.href] = { href: n.href, status: st,
          hits: [...new Set((t.match(/lead time|advance|hours? ahead|24 ?h|48 ?h|party|order type|booking horizon|horizon|collection window|min(?:imum)? notice|notice/gi) || []))],
          text: t.slice(0, 1400) };
        await look(o, jr, "owner", `${id}-${(n.label || "x").replace(/\W+/g, "-").slice(0, 18)}`, `owner page ${n.label}`);
      }
      fs.writeFileSync(`${OUT}/w020-owner.json`, JSON.stringify(rec.owner, null, 1));
      jr.note("> **Every page in that sidebar, read:**\n>\n> - "
            + Object.entries(rec.owner.probes).map(([k, v]) => `**${k}** \`${v.href}\` HTTP ${v.status} → ${v.hits.length ? v.hits.join(", ") : "nothing about lead times, order types, notice or a booking horizon"}`).join("\n> - "));
      const found = Object.entries(rec.owner.probes).filter(([, v]) => v.hits.length);
      return found.length
        ? `Lead-time / order-type words appear on: ${found.map(([k, v]) => `${k} (${v.hits.join(",")})`).join(" ; ")}`
        : `I walked every page in this kitchen's own sidebar (${rec.owner.nav.length}) and none mentions a lead time, an order type, a party order or a booking horizon.`;
    });

    await jr.step("OWNER", "I read the kitchen's own order board to see what a real window looks like", async id => {
      await o.goto(`${OWNER}/owner/experience/?restaurantId=${RID}`, { waitUntil: "domcontentloaded" });
      await o.waitForTimeout(DWELL + 2000);
      const t = await look(o, jr, "owner", id, "the kitchen's order board");
      const windows = (t.match(/\b(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s+\w+\s+\d+,\s*[\d:]+\s*[AP]M\s*[–-]\s*[\d:]+\s*[AP]M/g) || []);
      const modes = [...new Set(t.match(/\bPICKUP\b|\bDELIVERY\b/g) || [])];
      rec.owner.board = { windows: windows.slice(0, 8), modes, text: t.slice(0, 1200) };
      jr.note(`> **Collection windows the kitchen is actually holding right now:** ${windows.slice(0, 5).join(" | ") || "none on screen"}`);
      if (!windows.length) throw new Error("No collection window is shown on any order on this board.");
      return `The board shows ${modes.join(" and ")} orders carrying collection windows such as ${windows.slice(0, 3).join(" | ")}.`;
    });

    // ---------------- the diner half ----------------
    await jr.step("PATRON", "As a customer I cart something at this kitchen", async id => {
      await p.goto(`${PATRON}/menu?id=${RID}&table=${TABLE}`, { waitUntil: "domcontentloaded" });
      await p.waitForTimeout(DWELL);
      await answerDietary(p);
      await look(p, jr, "patron", id, "the kitchen's menu");
      const n = await p.locator('[data-testid^="m2-tile-"]').count();
      if (!n) throw new Error("Nothing on the menu.");
      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 basket");
      return `${n} dishes; one is in my basket.`;
    });

    await jr.step("PATRON", "I press Place order and try to reach the when-do-you-want-it screen", async id => {
      await tap(p, '[data-testid="m2-dock-trigger"]'); await p.waitForTimeout(2000);
      await tap(p, '[data-testid="m2-dock-cart"]');    await p.waitForTimeout(DWELL);
      await answerDietary(p, 3000);
      rec.patron.preOrderFulfilment = await fulfil(p);
      await tap(p, '[data-testid="order-now-btn"]');
      await p.waitForTimeout(DWELL);
      const t = await look(p, jr, "patron", id, "what Place order opened");
      rec.patron.afterOrder = { fulfil: await fulfil(p), gated: await p.locator('[data-testid="signup-gate"]').count(),
                                text: t.slice(0, 400) };
      jr.note(`> **Before I pressed order, anything about a pickup time:** ${rec.patron.preOrderFulfilment.join(", ") || "nothing"}`);
      if (!rec.patron.afterOrder.gated)
        return `No gate. On screen: ${rec.patron.afterOrder.fulfil.join(", ") || "nothing about a time"}.`;
      throw new Error("It asked who I am instead of when I want it.");
    });

    verdict = "WALKED"; why = "reached the picker";
    jr.finish(verdict, why);
  } catch (e) {
    why = String(e.message || e).split("\n")[0];

    await jr.soft("PATRON", "I sign in with the account this container was given, to get past the gate", async id => {
      await tap(p, '[data-testid="signup-gate-use-email"]'); await p.waitForTimeout(2500);
      await tap(p, '[data-testid="signup-gate-mode-signin"]'); await p.waitForTimeout(1500);
      const a = account("diner");
      await p.locator('[data-testid="signup-gate-email"]').first().fill(a.email);
      await p.locator('[data-testid="signup-gate-password"]').first().fill(a.password);
      await look(p, jr, "patron", id, "signing in");
      const sub = p.locator('[data-testid="signup-gate-email-submit"]').first();
      if (await sub.isDisabled().catch(() => false)) throw new Error("Sign in stays disabled with both boxes filled.");
      await sub.click({ timeout: 20000 });
      await p.waitForTimeout(DWELL * 2);
      const t = await look(p, jr, "patron", id, "after signing in");
      const err = (await p.locator('[data-testid="signup-gate-error"]').first().innerText().catch(() => "")).trim();
      rec.patron.signin = { err, gateStill: await p.locator('[data-testid="signup-gate"]').count(), screen: t.slice(0, 300) };
      if (err) throw new Error(`It refused me: "${err}" — so I cannot get to the picker at all.`);
      return `Screen now: ${t.slice(0, 220)}`;
    });

    await jr.soft("PATRON", "If I am through, I read the dates and times I am offered", async id => {
      const t = await look(p, jr, "patron", id, "the when-do-you-want-it screen");
      const f = await fulfil(p);
      rec.patron.picker = { fulfil: f, tids: await tids(p), text: t.slice(0, 1200) };
      fs.writeFileSync(`${OUT}/w020-patron.json`, JSON.stringify(rec.patron, null, 1));
      if (await p.locator('[data-testid="signup-gate"]').count())
        throw new Error("Still at the who-are-you panel — there is no picker to read.");
      if (!f.length) throw new Error(`Nothing on screen offers a pickup mode or a time. It reads: "${t.slice(0, 220)}"`);
      return `I am offered: ${f.join(", ")}. Screen: ${t.slice(0, 300)}`;
    });

    jr.finish("STOPPED", why);
  } finally {
    fs.writeFileSync(`${OUT}/w020.json`, JSON.stringify(rec, null, 1));
    await closeRole(oc, jr, "owner");
    await closeRole(pc, jr, "patron");
  }
});
