"""Phase 8 browser check: API keys (create, reveal once, call the public API, request log, rotate, revoke), outgoing
webhooks to a real local receiver (test event, live contact.created event, Standard Webhooks signature verified here
in Python, delivery log), analytics with CSV export, support tickets between a customer and platform support
(internal notes stay internal), notification email settings, system health, and mobile layouts.
Run against a freshly seeded DB (dev defaults allow webhook targets on 127.0.0.1):
    python e2e/e2e_platform.py ./e2e-shots
"""
import base64, hashlib, hmac, json, os, re, sys, threading, time
from http.server import BaseHTTPRequestHandler, HTTPServer
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")
errors = []
received = []

class Receiver(BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("content-length", 0))).decode()
        received.append({"headers": {k.lower(): v for k, v in self.headers.items()}, "body": body})
        self.send_response(200); self.end_headers(); self.wfile.write(b"ok")
    def log_message(self, *a):
        pass

srv = HTTPServer(("127.0.0.1", 0), Receiver)
threading.Thread(target=srv.serve_forever, daemon=True).start()
HOOK = f"http://127.0.0.1:{srv.server_address[1]}/devwebs"

def verify(secret, h, body):
    """Standard Webhooks verification, written independently of the server code."""
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{h['webhook-id']}.{h['webhook-timestamp']}.{body}".encode()
    expected = "v1," + base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return abs(time.time() - int(h["webhook-timestamp"])) < 300 and any(hmac.compare_digest(s, expected) for s in h["webhook-signature"].split(" "))

def wait_for(pred, timeout=10):
    end = time.time() + timeout
    while time.time() < end:
        if pred(): return True
        time.sleep(0.2)
    return False

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

def login(browser, email, viewport=(1440, 900)):
    ctx = browser.new_context(viewport={"width": viewport[0], "height": viewport[1]})
    page = ctx.new_page()
    page.on("pageerror", lambda e: errors.append(f"{email} pageerror: {e}"))
    page.on("console", lambda m: m.type == "error" and "Failed to load resource" not in m.text and errors.append(f"{email} console: {m.text}"))
    page.goto(f"{BASE}/login")
    page.fill("#email", email); page.fill("#password", "ChangeMe!2026"); page.click("button[type=submit]")
    page.wait_for_url(re.compile(r"/(dashboard|reseller)"))
    return ctx, page

with sync_playwright() as p:
    b = p.chromium.launch()
    ctx, page = login(b, "owner@acme.test")
    nav = page.get_by_role("navigation", name="Main")

    # ---- API keys ----
    nav.get_by_role("link", name="Developers").click()
    expect(page.get_by_text("No API keys yet")).to_be_visible()
    page.get_by_role("button", name="New API key").first.click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#k-name").fill("Shop sync")
    dlg.get_by_label("contacts:write").check()
    dlg.locator("#k-rate").fill("120")
    dlg.get_by_role("button", name="Create key").click()
    dlg = page.locator("dialog[open]")
    expect(dlg.get_by_text("You won't see it again")).to_be_visible()
    key = dlg.get_by_label("API key").inner_text().strip()
    assert re.fullmatch(r"dwk_live_[a-z0-9]{12}_[A-Za-z0-9_-]{43}", key), key
    shot(page, "x01-key-created")
    dlg.get_by_role("button", name="I've copied it").click()
    expect(page.get_by_text("dwk_live_")).to_be_visible()
    assert key not in page.content(), "the full key must not be shown again"

    api = p.request.new_context(base_url=BASE, extra_http_headers={"Authorization": f"Bearer {key}"})
    r = api.post("/api/v1/contacts", data={"first_name": "Api", "last_name": "Customer", "phone": "+919876500001"}, headers={"Idempotency-Key": "e2e-create-0001"})
    assert r.status == 201, r.text()
    r2 = api.post("/api/v1/contacts", data={"first_name": "Api", "last_name": "Customer", "phone": "+919876500001"}, headers={"Idempotency-Key": "e2e-create-0001"})
    assert r2.status == 201 and r2.headers.get("idempotent-replayed") == "true"
    assert api.get("/api/v1/contacts?q=Api").json()["data"][0]["first_name"] == "Api"
    assert api.get("/api/v1/campaigns").status == 403  # no campaigns scope

    page.reload()
    row = page.locator("li", has_text="Shop sync")
    row.get_by_role("button", name="Requests").click()
    dlg = page.locator("dialog[open]")
    expect(dlg.get_by_text("POST /api/v1/contacts").first).to_be_visible()
    expect(dlg.get_by_text("missing_scope")).to_be_visible()
    shot(page, "x02-request-log")
    dlg.get_by_role("button", name="Close").last.click()

    row.get_by_role("button", name="Rotate").click()
    page.locator("dialog[open]").locator("#k-grace").select_option("0")
    page.locator("dialog[open]").get_by_role("button", name="Rotate").click()
    new_key = page.locator("dialog[open]").get_by_label("API key").inner_text().strip()
    page.locator("dialog[open]").get_by_role("button", name="I've copied it").click()
    assert api.get("/api/v1/contacts").status == 401
    api2 = p.request.new_context(base_url=BASE, extra_http_headers={"Authorization": f"Bearer {new_key}"})
    assert api2.get("/api/v1/contacts").status == 200
    page.locator("li", has_text="Shop sync").get_by_role("button", name="Revoke").click()
    page.locator("dialog[open]").get_by_role("button", name="Revoke key").click()
    expect(page.get_by_text("Revoked", exact=True)).to_be_visible()
    assert api2.get("/api/v1/contacts").status == 401

    # ---- Webhooks ----
    page.get_by_role("navigation", name="Developers").get_by_role("link", name="Webhooks").click()
    page.get_by_role("button", name="Add endpoint").first.click()
    dlg = page.locator("dialog[open]")
    dlg.get_by_label("Endpoint URL").fill(HOOK)
    dlg.get_by_label("Description (optional)").fill("Order system")
    dlg.get_by_role("button", name="Add endpoint").click()
    secret = page.locator("dialog[open]").get_by_label("Signing secret").inner_text().strip()
    assert secret.startswith("whsec_")
    page.locator("dialog[open]").get_by_role("button", name="Done").click()
    page.locator("a", has_text=HOOK).click()
    page.wait_for_url(re.compile(r"/developers/webhooks/\d+$"))
    endpoint_url = page.url
    page.get_by_role("button", name="Send test event").click()
    expect(page.get_by_text("Test event delivered (HTTP 200)")).to_be_visible()
    assert received and received[-1]["headers"]["devwebs-event-type"] == "test.ping"
    assert verify(secret, received[-1]["headers"], received[-1]["body"]), "test event signature"

    # A real change in the app becomes a signed event at the receiver.
    nav.get_by_role("link", name="Contacts").click()
    page.get_by_role("button", name="Add contact").first.click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#cf-first").fill("Webhook")
    dlg.locator("#cf-phone").fill("+91 98765 00002")
    dlg.get_by_role("button", exact=True, name="Add contact").click()
    expect(page.get_by_text("Contact added")).to_be_visible()
    assert wait_for(lambda: any(r["headers"].get("devwebs-event-type") == "contact.created" and "Webhook" in r["body"] for r in received)), "contact.created not received"
    ev = next(r for r in received if r["headers"].get("devwebs-event-type") == "contact.created" and "Webhook" in r["body"])
    assert verify(secret, ev["headers"], ev["body"]), "contact.created signature"
    payload = json.loads(ev["body"])
    assert payload["type"] == "contact.created" and payload["data"]["contact"]["phone"].endswith("9876500002") and payload["id"] == ev["headers"]["webhook-id"]

    page.goto(endpoint_url)
    page.get_by_role("button", name=re.compile("contact.created")).first.click()
    dlg = page.locator("dialog[open]")
    expect(dlg.get_by_text("HTTP 200").first).to_be_visible()
    expect(dlg.get_by_text('"type": "contact.created"')).to_be_visible()
    shot(page, "x03-delivery")
    dlg.get_by_role("button", name="Close").last.click()

    # ---- Analytics ----
    nav.get_by_role("link", name="Analytics").click()
    expect(page.get_by_text("Messages per day")).to_be_visible()
    page.get_by_role("button", name="Show table").click()
    expect(page.get_by_role("columnheader", name="Received")).to_be_visible()
    page.get_by_role("button", name="7 days").click()
    with page.expect_download() as dl:
        page.get_by_role("link", name="Export CSV").first.click()
    assert dl.value.suggested_filename.startswith("devwebs-messaging-")
    page.get_by_role("tab", name="Usage and wallet").click()
    expect(page.get_by_text("Wallet transactions")).to_be_visible()
    shot(page, "x04-analytics", full=True)

    # ---- Support (customer) ----
    nav.get_by_role("link", name="Support").click()
    expect(page.get_by_text("How do I add a second WhatsApp number?")).to_be_visible()
    page.get_by_role("button", name="New ticket").click()
    dlg = page.locator("dialog[open]")
    dlg.locator("#tk-subject").fill("Campaign stuck at sending")
    dlg.locator("#tk-pri").select_option("high")
    dlg.locator("#tk-body").fill("Our Diwali campaign has shown Sending for an hour.")
    dlg.get_by_role("button", name="Send to support").click()
    page.wait_for_url(re.compile(r"/support/\d+$"))
    ticket_url = page.url
    expect(page.get_by_text("Waiting for support")).to_be_visible()

    # ---- Platform support ----
    actx, admin = login(b, "admin@devwebs.test")
    anav = admin.get_by_role("navigation", name="Main")
    anav.get_by_role("link", name="Support tickets").click()
    admin.get_by_role("link", name=re.compile("Campaign stuck at sending")).click()
    admin.get_by_label("Internal note", exact=True).check()
    admin.locator("#tk-reply").fill("Worker was restarted at 10:40; check queue depth.")
    admin.get_by_role("button", name="Add note").click()
    expect(admin.get_by_text("Note added")).to_be_visible()
    admin.get_by_label("Internal note", exact=True).uncheck()
    admin.locator("#tk-reply").fill("It's sending again now. Thanks for flagging it!")
    admin.get_by_role("button", name="Send reply").click()
    expect(admin.get_by_text("Reply sent")).to_be_visible()
    expect(admin.get_by_text("Waiting for customer").first).to_be_visible()
    shot(admin, "x05-admin-ticket")
    anav.get_by_role("link", name="System health").click()
    expect(admin.get_by_text("Background workers")).to_be_visible()
    expect(admin.get_by_text("Campaign sending")).to_be_visible()
    shot(admin, "x06-health", full=True)

    page.goto(ticket_url)
    expect(page.get_by_text("It's sending again now.")).to_be_visible()
    assert "Worker was restarted" not in page.content(), "internal note leaked to the customer"
    expect(page.get_by_text("Waiting for you")).to_be_visible()
    page.get_by_role("button", name=re.compile("Notifications")).click()
    expect(page.get_by_text(re.compile("Devwebs support replied on #\\d+"))).to_be_visible()
    page.get_by_role("link", name="Email settings").click()
    page.wait_for_url("**/settings/notifications")
    expect(page.get_by_text("Email sending isn't set up")).to_be_visible()
    sw = page.get_by_role("switch", name="Email me about billing")
    expect(sw).to_have_attribute("aria-checked", "true")
    sw.click()
    expect(page.get_by_text("Billing emails off")).to_be_visible()
    page.reload()
    expect(page.get_by_role("switch", name="Email me about billing")).to_have_attribute("aria-checked", "false")

    # ---- Mobile ----
    mctx = b.new_context(viewport={"width": 390, "height": 844}, storage_state=ctx.storage_state())
    mp = mctx.new_page()
    mp.on("pageerror", lambda e: errors.append(f"mobile pageerror: {e}"))
    for path in ["/analytics", "/developers", "/developers/webhooks", "/developers/reference", "/support", "/settings/notifications", ticket_url.replace(BASE, "")]:
        mp.goto(f"{BASE}{path}")
        mp.wait_for_timeout(900)
        assert mp.evaluate("document.documentElement.scrollWidth <= window.innerWidth"), f"{path} overflows on mobile"
    shot(mp, "x07-mobile-analytics", full=True)
    amctx = b.new_context(viewport={"width": 390, "height": 844}, storage_state=actx.storage_state())
    amp = amctx.new_page()
    for path in ["/admin/tickets", "/admin/health"]:
        amp.goto(f"{BASE}{path}")
        amp.wait_for_timeout(900)
        assert amp.evaluate("document.documentElement.scrollWidth <= window.innerWidth"), f"{path} overflows on mobile"
    b.close()

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