"""Phase 5 browser check: bot replies (create text/interactive with upload progress, validation, trigger conflicts,
tester, automatic replies arriving in the inbox, pausing automation, switching off) and the flow builder (example
flow, validate, edit, connect, save, publish, versions, staff read-only). Run against a freshly seeded DB with
WHATSAPP_PROVIDER=fake:
    python e2e/e2e_bots.py ./e2e-shots
"""
import base64, os, re, sys, time
from playwright.sync_api import sync_playwright, expect

OUT = sys.argv[1] if len(sys.argv) > 1 else "./e2e-shots"
os.makedirs(OUT, exist_ok=True)
BASE = os.environ.get("E2E_BASE", "http://localhost:3000")
HDR = {"X-Requested-With": "devwebs"}
PNG = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==")
errors = []

def shot(page, name, full=False):
    page.screenshot(path=f"{OUT}/{name}.png", full_page=full)

with sync_playwright() as p:
    b = p.chromium.launch()
    ctx = b.new_context(viewport={"width": 1440, "height": 900})
    page = ctx.new_page()
    page.on("pageerror", lambda e: errors.append(f"pageerror: {e}"))
    page.on("console", lambda m: m.type == "error" and "Failed to load resource" not in m.text and errors.append(f"console: {m.text}"))
    page.goto(f"{BASE}/login")
    page.fill("#email", "owner@acme.test"); page.fill("#password", "ChangeMe!2026"); page.click("button[type=submit]")
    page.wait_for_url("**/dashboard")
    api = ctx.request
    acc = api.post(f"{BASE}/api/whatsapp/accounts", headers=HDR, data={"waba_id": "102290129340398", "phone_number_id": "106540352242922", "access_token": "EAAG" + "x" * 60 + "WXYZ", "app_secret": "a" * 32}).json()["data"]
    def customer_says(text, frm="919812345670", name="Rohan Mehta"):
        r = api.post(f"{BASE}/api/whatsapp/accounts/{acc['id']}/simulate", headers=HDR, data={"kind": "message", "from": frm, "name": name, "text": text})
        assert r.ok, r.text()

    page.reload()
    expect(page.get_by_role("link", name="Set up bot replies")).to_be_visible()
    nav = page.get_by_role("navigation", name="Main")
    nav.get_by_role("link", name="Bot replies").click()
    expect(page.get_by_text("No bot replies yet")).to_be_visible()

    # ---- Text reply with validation and variables ----
    page.get_by_role("button", name="New bot reply").first.click()
    page.get_by_role("menuitem", name="Text reply").click()
    dlg = page.locator("dialog[open]")
    dlg.get_by_role("button", name="Create reply").click()
    expect(dlg.get_by_text("Name must be at least 2 characters")).to_be_visible()
    expect(dlg.get_by_text("Enter the text that triggers this reply")).to_be_visible()
    expect(dlg.get_by_text("Reply text is required")).to_be_visible()
    dlg.locator("#br-name").fill("Prices")
    dlg.locator("#br-ts").fill("price")
    dlg.locator("#rc-body").fill("Hi ")
    dlg.get_by_role("button", name="{first_name}").click()
    dlg.locator("#rc-body").press("End")
    dlg.locator("#rc-body").type(", prices start at ₹499.")
    expect(dlg.locator("#rc-body")).to_have_value("Hi {first_name}, prices start at ₹499.")
    expect(dlg.get_by_text("Hi Asha, prices start at ₹499.")).to_be_visible()
    shot(page, "b01-text-reply")
    dlg.get_by_role("button", name="Create reply").click()
    expect(page.get_by_text("Created Prices")).to_be_visible()
    expect(page.get_by_role("button", name="Prices", exact=True)).to_be_visible()

    # ---- Same trigger again is refused ----
    page.get_by_role("button", name="New bot reply").first.click()
    page.get_by_role("menuitem", name="Text reply").click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#br-name").fill("Prices again")
    dlg.locator("#br-ts").fill("Price!")
    dlg.locator("#rc-body").fill("x")
    dlg.get_by_role("button", name="Create reply").click()
    expect(dlg.get_by_text(re.compile("Bot reply “Prices” already uses this trigger"))).to_be_visible()
    dlg.get_by_role("button", name="Cancel").click()

    # ---- Interactive reply with an uploaded image header ----
    page.get_by_role("button", name="New bot reply").first.click()
    page.get_by_role("menuitem", name="Interactive reply").click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#br-name").fill("Support menu")
    dlg.locator("#br-tt").select_option("contains_word")
    expect(dlg.get_by_text(re.compile("“ship” matches “do you ship"))).to_be_visible()
    dlg.locator("#br-ts").fill("help")
    dlg.locator("#rc-ht").select_option("image")
    dlg.get_by_label("Upload image").set_input_files({"name": "menu.png", "mimeType": "image/png", "buffer": PNG})
    expect(dlg.get_by_text("Uploaded")).to_be_visible()
    dlg.locator("#rc-body").fill("How can we help, {first_name}?")
    dlg.get_by_label("Button 1 label").fill("Billing")
    dlg.get_by_role("button", name="Add button").click()
    dlg.get_by_label("Button 2 label").fill("Technical")
    expect(dlg.get_by_text("How can we help, Asha?")).to_be_visible()
    shot(page, "b02-interactive-reply")
    dlg.get_by_role("button", name="Create reply").click()
    expect(page.get_by_text("Created Support menu")).to_be_visible()

    # ---- Tester ----
    page.get_by_label("Message to test").fill("PRICE?")
    page.get_by_role("button", name="Check").click()
    expect(page.get_by_text("Matched: Is exactly “price”")).to_be_visible()
    shot(page, "b03-replies-list")

    # ---- Replies arrive in the inbox ----
    customer_says("price")
    customer_says("I need some help please")
    time.sleep(1)
    nav.get_by_role("link", name="Inbox").click()
    page.get_by_role("button", name=re.compile("Rohan Mehta")).first.click()
    chat = page.get_by_role("region", name="Conversation with Rohan Mehta")
    expect(chat.get_by_text("Hi Rohan, prices start at ₹499.")).to_be_visible()
    expect(chat.get_by_text("How can we help, Rohan?")).to_be_visible()
    expect(chat.get_by_text("Bot reply").first).to_be_visible()
    expect(chat.locator("div", has_text=re.compile(r"^Technical$")).first).to_be_visible()
    shot(page, "b04-inbox-bot-replies")
    # Human takes over: automation pauses for this chat only.
    page.get_by_role("switch").filter(visible=True).first.uncheck()
    expect(page.get_by_text("Automated replies paused for this chat")).to_be_visible()
    before = chat.get_by_text("Hi Rohan, prices start at ₹499.").count()
    customer_says("price")
    expect(chat.get_by_text("price", exact=True)).to_have_count(2)
    time.sleep(1.5)
    assert chat.get_by_text("Hi Rohan, prices start at ₹499.").count() == before, "bot replied in a paused chat"

    # ---- Switch a reply off; test-send dialog ----
    nav.get_by_role("link", name="Bot replies").click()
    page.get_by_label("Prices active").uncheck()
    expect(page.get_by_label("Prices active")).not_to_be_checked()
    page.get_by_role("button", name="Send Support menu as a test").click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#ts-to").select_option(index=1)
    dlg.get_by_role("button", name="Send test").click()
    expect(page.get_by_text("Sent Support menu as a test")).to_be_visible()

    # ---- Flow builder ----
    nav.get_by_role("link", name="Bot flows").click()
    expect(page.get_by_text("No flows yet")).to_be_visible()
    page.get_by_role("button", name="Add example flow").first.click()
    page.wait_for_url(re.compile(r"/bot-flows/\d+$"))
    expect(page.get_by_role("heading", name="Account support")).to_be_visible()
    expect(page.get_by_text("Starts when the message is exactly “I have an account inquiry.”")).to_be_visible()
    canvas = page.get_by_label("Flow canvas")
    for label in ["Account menu", "Billing Issue", "Technical Issue", "Transaction Issue", "Thank you", "Visit again", "End conversation", "Support agent"]:
        expect(canvas.get_by_text(label, exact=True).first).to_be_attached()
    page.get_by_role("button", name="Validate").click()
    expect(page.get_by_text("Everything checks out.")).to_be_visible()
    shot(page, "b05-builder")

    # Edit a step: rename and break it, validation points at it.
    page.locator(".react-flow__node").filter(has_text="Charges appear").click()
    panel = page.get_by_label("Text message settings")
    panel.locator("#pp-label").fill("Charge help")
    panel.locator("[id$='-body']").fill("")
    page.get_by_role("button", name="Validate").click()
    expect(page.get_by_text("Reply text is required")).to_be_visible()
    shot(page, "b06-validation")
    page.get_by_role("button", name=re.compile("^Charge help")).click()
    panel = page.get_by_label("Text message settings")
    panel.locator("[id$='-body']").fill("Check Settings > Billing for the matching invoice.")

    # Add a step and connect it by dragging between handles.
    page.get_by_role("button", name="Add step").click()
    page.get_by_role("menuitem", name=re.compile("^Text message")).click()
    panel = page.get_by_label("Text message settings")
    panel.locator("#pp-label").fill("Extra tip")
    panel.locator("[id$='-body']").fill("Tip: save your invoices as PDF.")
    page.get_by_role("button", name="Close step settings").click()
    page.get_by_role("button", name="Fit View").click()
    new_node = page.locator(".react-flow__node").filter(has_text="Extra tip")
    source = page.locator(".react-flow__node").filter(has_text="Visit again").locator(".react-flow__handle-right")
    # Visit again → End already exists; connect Visit again's output to the new step instead (replaces the old edge).
    target = new_node.locator(".react-flow__handle-left")
    sb, tb = source.bounding_box(), target.bounding_box()
    page.mouse.move(sb["x"] + sb["width"] / 2, sb["y"] + sb["height"] / 2)
    page.mouse.down()
    page.mouse.move(tb["x"] + tb["width"] / 2, tb["y"] + tb["height"] / 2, steps=12)
    page.mouse.up()
    expect(page.get_by_text("Unsaved changes")).to_be_visible()
    page.get_by_role("button", name="Save").click()
    expect(page.get_by_role("button", name="Save")).to_be_disabled()
    flow_id = int(page.url.rstrip("/").split("/")[-1])
    flow = api.get(f"{BASE}/api/bot-flows/{flow_id}").json()["data"]
    extra = next(n for n in flow["draft"]["nodes"] if n["data"].get("label") == "Extra tip")
    visit = next(n for n in flow["draft"]["nodes"] if n["data"].get("label") == "Visit again")
    outs = [e for e in flow["draft"]["edges"] if e["source"] == visit["id"]]
    assert len(outs) == 1 and outs[0]["target"] == extra["id"], outs

    # Publish, versions, engine switch.
    page.get_by_role("button", name="Publish").click()
    page.locator("dialog[open]").locator("#pub-note").fill("First release")
    page.locator("dialog[open]").get_by_role("button", name="Publish").click()
    expect(page.get_by_text("Published version 1")).to_be_visible()
    expect(page.get_by_text("v1", exact=True)).to_be_visible()
    expect(page.get_by_role("switch", name="Flow on")).to_be_enabled()
    page.get_by_role("button", name="Version history").click()
    expect(page.locator("dialog[open]").get_by_text("First release")).to_be_visible()
    shot(page, "b07-versions")
    page.keyboard.press("Escape")

    # Delete a step with the node's delete button; leaving with unsaved changes asks first.
    new_node.hover()
    page.get_by_role("button", name="Delete Extra tip").click()
    expect(page.locator(".react-flow__node").filter(has_text="Extra tip")).to_have_count(0)
    page.get_by_role("link", name="Back to bot flows").click()
    expect(page.locator("dialog[open]").get_by_text("Leave without saving?")).to_be_visible()
    page.locator("dialog[open]").get_by_role("button", name="Leave").click()
    page.wait_for_url("**/bot-flows")
    expect(page.get_by_text("Version 1")).to_be_visible()

    # ---- Staff: read-only builder ----
    r = api.post(f"{BASE}/api/team", headers=HDR, data={"name": "Sam Staff", "email": "sam@acme.test", "password": "ChangeMe!2026", "role": "customer_agent"})
    assert r.ok, r.text()
    s_ctx = b.new_context(viewport={"width": 1280, "height": 800})
    sp = s_ctx.new_page()
    sp.on("pageerror", lambda e: errors.append(f"staff pageerror: {e}"))
    sp.goto(f"{BASE}/login"); sp.fill("#email", "sam@acme.test"); sp.fill("#password", "ChangeMe!2026"); sp.click("button[type=submit]")
    sp.wait_for_url("**/dashboard")
    sp.goto(f"{BASE}/bot-replies")
    expect(sp.get_by_role("button", name="Support menu")).to_be_visible()
    expect(sp.get_by_role("button", name="New bot reply")).to_have_count(0)
    sp.goto(f"{BASE}/bot-flows/{flow_id}")
    expect(sp.get_by_role("heading", name="Account support")).to_be_visible()
    expect(sp.get_by_role("button", name="Publish")).to_have_count(0)
    expect(sp.get_by_role("button", name="Add step")).to_have_count(0)

    # ---- Mobile ----
    m = b.new_context(viewport={"width": 390, "height": 844}, storage_state=ctx.storage_state())
    mp = m.new_page()
    mp.on("pageerror", lambda e: errors.append(f"mobile pageerror: {e}"))
    for path in ["/bot-replies", "/bot-flows"]:
        mp.goto(f"{BASE}{path}")
        mp.wait_for_timeout(800)
        assert mp.evaluate("document.documentElement.scrollWidth <= window.innerWidth"), f"{path} overflows on mobile"
    shot(mp, "b08-mobile-flows")
    mp.goto(f"{BASE}/bot-flows/{flow_id}")
    expect(mp.get_by_role("heading", name="Account support")).to_be_visible()
    assert mp.evaluate("document.documentElement.scrollWidth <= window.innerWidth"), "builder overflows on mobile"
    mp.locator(".react-flow__node").filter(has_text="Start").first.click()
    expect(mp.get_by_text("This flow starts when")).to_be_visible()
    shot(mp, "b09-mobile-builder")
    b.close()

if errors:
    print("\n".join(errors)); sys.exit(1)
print("bots e2e passed")
