"""Drive a real form login in headless Chrome and report where it lands.

Token injection skips the login screen entirely, so it can't catch bugs in the
login round trip.  This types into the actual form and clicks the button.

  usage: python3 logintest.py <url> <email> <password>
"""
import json, os, subprocess, sys, time, urllib.request

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import importlib.util
spec = importlib.util.spec_from_file_location("cdpmod", os.path.join(
    os.path.dirname(os.path.abspath(__file__)), "cdp.py"))

URL = sys.argv[1]
EMAIL = sys.argv[2]
PASSWORD = sys.argv[3]
CHROME = ("/tmp/claude-0/-root-alhang/4ab098a2-a126-4502-b30c-c3d7b59e8765/scratchpad/"
          "chs/chrome-headless-shell-linux64/chrome-headless-shell")
PORT = 9223

# Reuse cdp.py's WS class without running its script body.
src = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "cdp.py")).read()
ns = {}
exec(src[src.index("class WS"):src.index("EVENTS = []")], {
    "base64": __import__("base64"), "json": json, "secrets": __import__("secrets"),
    "socket": __import__("socket"), "struct": __import__("struct"),
    "time": time, "EVENTS": []}, ns)
WS = ns["WS"]

proc = subprocess.Popen(
    [CHROME, "--remote-debugging-port=%d" % PORT, "--no-sandbox", "--disable-gpu",
     "--disable-dev-shm-usage", "--window-size=1280,900", "about:blank"],
    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
    target = None
    for _ in range(50):
        time.sleep(0.4)
        try:
            with urllib.request.urlopen("http://127.0.0.1:%d/json" % PORT, timeout=3) as r:
                tabs = json.loads(r.read())
            pages = [t for t in tabs if t.get("type") == "page"]
            if pages:
                target = pages[0]; break
        except Exception:
            continue
    if not target:
        print("could not reach devtools"); raise SystemExit(2)

    ws = WS(target["webSocketDebuggerUrl"])
    ws.call("Runtime.enable")
    ws.call("Network.enable")
    ws.call("Page.enable")

    def ev(expr, timeout=25):
        r = ws.call("Runtime.evaluate", {"expression": expr, "returnByValue": True,
                                         "awaitPromise": True}, timeout=timeout)
        res = r.get("result", {})
        if res.get("exceptionDetails"):
            return "EXC: " + json.dumps(res["exceptionDetails"])[:200]
        return res.get("result", {}).get("value")

    print("navigating to", URL)
    ws.call("Page.navigate", {"url": URL})
    time.sleep(14)          # let the SPA boot and code-split

    print("before login:", ev("document.title"), "|",
          ev("location.pathname"))
    print("inputs on page:",
          ev("Array.from(document.querySelectorAll('input'))"
             ".map(function(i){return i.name||i.type}).join(',')"))

    # React tracks input value on the DOM node; assigning .value directly is
    # invisible to it.  Go through the native setter, then fire the event React
    # actually listens for.
    fill = """
    (function(){
      function set(el, v){
        var d = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value');
        d.set.call(el, v);
        el.dispatchEvent(new Event('input', {bubbles:true}));
        el.dispatchEvent(new Event('change', {bubbles:true}));
      }
      var ins = Array.from(document.querySelectorAll('input'));
      var pw = ins.filter(function(i){return i.type === 'password';})[0];
      var em = ins.filter(function(i){return i.type !== 'password' &&
               (i.name === 'email' || i.type === 'text' || i.type === 'email');})[0];
      if (!em || !pw) { return 'fields not found: ' + ins.length; }
      set(em, %s); set(pw, %s);
      return 'filled';
    })()
    """ % (json.dumps(EMAIL), json.dumps(PASSWORD))
    print("fill:", ev(fill))
    time.sleep(1)

    click = """
    (function(){
      var b = Array.from(document.querySelectorAll('button'))
        .filter(function(x){return /log ?in/i.test(x.textContent||'');})[0];
      if (!b) {
        var f = document.querySelector('form');
        if (f) { f.requestSubmit ? f.requestSubmit() : f.submit(); return 'submitted form'; }
        return 'no button';
      }
      b.click();
      return 'clicked: ' + b.textContent.trim();
    })()
    """
    print("click:", ev(click))

    time.sleep(18)          # login round trip + gateway connect + render

    print("\n--- after login ---")
    print("url      :", ev("location.pathname"))
    print("title    :", ev("document.title"))
    print("has token:", ev("!!localStorage.getItem('token')"))
    print("visible  :", json.dumps(ev(
        "(document.body.innerText||'').replace(/\\n+/g,' | ').slice(0,700)"))[:900])
finally:
    proc.terminate()
