import { test, expect } from "@playwright/test";
import { journal, look, arriveAsDiner, roleContext, closeRole, table, PATRON, DWELL } from "../lib-witness.mjs";

const RID  = "7766d206-244c-576d-be4b-f5e746bf9832";
const T    = table(10097);
const SLUG = "w043-pay-complete";
const MONEY = /\$\s?\d[\d,]*(?:\.\d{2})?/g;
const money = (s) => Array.from(new Set((s || "").match(MONEY) || []));

const wire = [];
const watch = (p, who) => p.on("response", r => {
  if (/payments?\/(intent|summary|confirm)|settle|charge/i.test(r.url()))
    wire.push({ who, status: r.status(), u: r.url().slice(0, 200) });
});
const sheet = async (p, tid) => {
  const l = p.getByTestId(tid);
  return (await l.count()) ? (await l.first().innerText()).replace(/\s+/g, " ").trim() : "";
};
async function openBubble(p) {
  const b = p.getByTestId("layout-service-button");
  if (await b.count()) await b.first().evaluate(el => el.click());
  else await p.evaluate(() => window.dispatchEvent(new CustomEvent("qrate:open-service-bubble")));
  await p.waitForTimeout(3000);
}

test(SLUG, async ({ browser }) => {
  const jr = journal(SLUG);
  jr.target({ app: "patron mosaic_v2", baseUrl: PATRON, restaurantId: RID, table: T });
  jr.note("**Going all the way this time.** The card form DOES open — you have to tap the row that says *Card* first, which my earlier walk never did. Stripe is in **test mode** (`pk_test_`), this is dev, and the scheduled canary charges this same fixture every half hour. So I complete the payment with the 4242 test card and then check the one thing nobody has checked: does the table's check afterwards show only what is still unpaid?");

  const c1 = await roleContext(browser, jr, "diner-1");
  const c2 = await roleContext(browser, jr, "diner-2");
  const p1 = await c1.newPage(); watch(p1, "diner-1");
  const p2 = await c2.newPage(); watch(p2, "diner-2");
  let verdict = "INCOMPLETE", why = "", paid = false;
  const seen = {};

  try {
    await jr.soft("BOTH", "two of us sit down and each order a drink", async id => {
      await arriveAsDiner(p1, RID, T, "mosaic_v2");
      await arriveAsDiner(p2, RID, T, "mosaic_v2");
      await p1.locator('[data-testid^="m2-add-"]').first().evaluate(el => el.click());
      await p2.locator('[data-testid^="m2-add-"]').nth(1).evaluate(el => el.click());
      await p1.waitForTimeout(2500); await p2.waitForTimeout(2500);
      for (const p of [p1, p2]) {
        await p.getByTestId("m2-view-order").first().evaluate(el => el.click());
        await p.waitForTimeout(2500);
        await p.getByTestId("order-now-btn").first().evaluate(el => el.click());
        await p.waitForTimeout(7000);
      }
      const t = await look(p1, jr, "diner-1", id, "both orders sent");
      await look(p2, jr, "diner-2", id, "her order sent");
      return t.slice(0, 200);
    });

    await jr.soft("DINER 1", "I ask for the check and choose to pay only for myself", async id => {
      await openBubble(p1);
      await p1.getByTestId("service-bubble-ready_for_bill").first().evaluate(el => el.click());
      await p1.waitForTimeout(4000);
      const cover = p1.getByTestId("settlement-cover");
      if (await cover.count()) { await cover.first().evaluate(el => el.click()); await p1.waitForTimeout(3000); }
      const me = p1.getByTestId("cover-scope-just-me");
      if (!(await me.count())) throw new Error(`no "just me" choice: ${await sheet(p1, "service-bubble")}`);
      await me.first().evaluate(el => el.click());
      await p1.waitForTimeout(3500);
      await look(p1, jr, "diner-1", id, "covering my own items");
      return await sheet(p1, "service-bubble");
    });

    await jr.soft("DINER 1", "the check I am shown is the one I asked for", async id => {
      await p1.getByTestId("payhow-pay-now").first().evaluate(el => el.click());
      await p1.waitForTimeout(12000);
      seen.check = await sheet(p1, "pay-your-check");
      seen.checkLines = await p1.getByTestId("pyc-line-item").allInnerTexts().catch(() => []);
      seen.caption = await sheet(p1, "pyc-line-items-caption");
      seen.amountDue = await sheet(p1, "pyc-amount-due");
      seen.total = await sheet(p1, "pyc-total");
      await look(p1, jr, "diner-1", id, "the check");
      if (!seen.amountDue) throw new Error(`no amount due on the check: ${seen.check}`);
      return `caption "${seen.caption}" | lines ${JSON.stringify(seen.checkLines)} | amount due ${seen.amountDue} | total ${seen.total}`;
    });

    await jr.soft("DINER 1", "I tap Card, type my card and pay", async id => {
      await p1.getByTestId("pyc-continue").first().evaluate(el => el.click());
      await p1.waitForTimeout(14000);
      let f = null;
      for (const fr of p1.frames()) {
        if (!/js\.stripe\.com/i.test(fr.url())) continue;
        const row = fr.getByText(/^\s*Card\s*$/i).first();
        if (await row.count().catch(() => 0)) { await row.click({ timeout: 10000 }).catch(async () => row.evaluate(e => e.click())); f = fr; break; }
      }
      if (!f) throw new Error("could not find the Card row to tap");
      await p1.waitForTimeout(6000);
      await look(p1, jr, "diner-1", id + "a", "card fields open");
      for (const [sel, val] of [['input[name="number"]', "4242424242424242"],
                                ['input[name="expiry"]', "1234"],
                                ['input[name="cvc"]', "123"],
                                ['input[name="postalCode"]', "42424"]]) {
        const l = f.locator(sel).first();
        if (await l.count()) await l.fill(val);
      }
      await p1.waitForTimeout(2500);
      const btn = p1.getByTestId("pyc-pay-btn").first();
      seen.payBtn = { text: (await btn.innerText()).trim(), disabled: await btn.isDisabled() };
      if (seen.payBtn.disabled) throw new Error(`card entered, Pay still greyed out: ${JSON.stringify(seen.payBtn)}`);
      await look(p1, jr, "diner-1", id + "b", "ready to pay");
      await btn.click();
      paid = true;
      await p1.waitForTimeout(30000);
      const t = await look(p1, jr, "diner-1", id, "after paying");
      seen.afterPay = (await sheet(p1, "pay-your-check")) || t;
      seen.afterPayIds = await p1.evaluate(() => Array.from(document.querySelectorAll("[data-testid]")).map(e => e.getAttribute("data-testid")).filter(x => /pyc-|pay|receipt|settle|service/.test(x)).sort());
      if (!/paid|thank|receipt|success|complete|settled|all set/i.test(String(seen.afterPay)))
        throw new Error(`payment submitted but nothing on screen confirms it: "${String(seen.afterPay).slice(0,300)}" | ${JSON.stringify(seen.afterPayIds)}`);
      return `pressed ${JSON.stringify(seen.payBtn)} -> "${String(seen.afterPay).slice(0,400)}"`;
    });

    await jr.soft("DINER 1", "my own phone now shows me as settled", async id => {
      await p1.reload(); await p1.waitForTimeout(DWELL);
      await openBubble(p1);
      seen.p1After = await sheet(p1, "service-bubble");
      await look(p1, jr, "diner-1", id, "my drawer after paying");
      const cp = p1.getByTestId("service-bubble-ready_for_bill");
      seen.p1CheckStillOffered = await cp.count() ? !(await cp.first().isDisabled().catch(() => false)) : false;
      if (!/paid|settled|thank/i.test(seen.p1After))
        throw new Error(`after paying, my drawer says nothing about it: "${seen.p1After}" (Check Please still offered: ${seen.p1CheckStillOffered})`);
      return seen.p1After;
    });

    await jr.soft("DINER 2", "her check now shows only what is still unpaid", async id => {
      await p2.reload(); await p2.waitForTimeout(DWELL);
      await openBubble(p2);
      const cp = p2.getByTestId("service-bubble-ready_for_bill");
      if (!(await cp.count())) throw new Error(`she has no Check Please control: ${await sheet(p2, "service-bubble")}`);
      await cp.first().evaluate(el => el.click());
      await p2.waitForTimeout(4000);
      const cover = p2.getByTestId("settlement-cover");
      if (await cover.count()) { await cover.first().evaluate(el => el.click()); await p2.waitForTimeout(3000); }
      const me = p2.getByTestId("cover-scope-just-me");
      if (await me.count()) { await me.first().evaluate(el => el.click()); await p2.waitForTimeout(3500); }
      const pay = p2.getByTestId("payhow-pay-now");
      if (!(await pay.count())) throw new Error(`she is not offered a way to pay: ${await sheet(p2, "service-bubble")}`);
      await pay.first().evaluate(el => el.click());
      await p2.waitForTimeout(12000);
      seen.p2Check = await sheet(p2, "pay-your-check");
      seen.p2Lines = await p2.getByTestId("pyc-line-item").allInnerTexts().catch(() => []);
      seen.p2AmountDue = await sheet(p2, "pyc-amount-due");
      await look(p2, jr, "diner-2", id, "her check after I paid mine");
      seen.p2Money = money(seen.p2Check);
      if (!seen.p2AmountDue) throw new Error(`her check shows no amount: "${seen.p2Check}"`);
      return `HER CHECK: lines ${JSON.stringify(seen.p2Lines)} | amount due ${seen.p2AmountDue} | full text "${seen.p2Check}"`;
    });

    verdict = "WALKED";
    why = `Completed a real test-mode card payment as one diner of two on table ${T}, then read the other diner's check.`;
  } catch (e) { why = String(e.message || e).split("\n")[0]; }
  finally {
    jr.note("### The payment wire\n\n```\n" + wire.map(x => `${x.who} ${x.status} ${x.u}`).join("\n").slice(0, 3000) + "\n```");
    jr.note("### Everything read off the screens\n\n```json\n" + JSON.stringify(seen, null, 1).slice(0, 6000) + "\n```");
    jr.note(`> **What I left behind:** table ${T} on ${RID} — two guests, two placed orders, and ${paid ? "ONE COMPLETED TEST-MODE CARD PAYMENT (Stripe pk_test_, 4242 test card) for diner 1's share" : "no payment"}. Diner 2's item is unpaid.`);
    jr.finish(verdict, why);
    await closeRole(c1, jr, "diner-1");
    await closeRole(c2, jr, "diner-2");
  }
  expect(verdict, why).not.toBe("INCOMPLETE");
});
