#!/usr/bin/env python3
"""
Generic 4-test spike validator.

Tests:
  A: Vanilla headless, no input (control — expect "no event" baseline)
  B: CDP-attached,   no input (control — expect "no event" baseline)
  C: Vanilla headless, with input (test vanilla's input path)
  D: CDP-attached,   with input (test CDP's input path)

If C fires but D doesn't, the signal is a CDP-input discriminator.
If both C and D fire (or both don't), no CDP discrimination.

Usage: python3 test4.py <spike-name> <button-id> <wait-ms> [<inject-script>]

The inject-script is an optional JS string run inside the page during the
input-injection phase. For mouse: pass 'mouse:60' (60 page.mouse.move calls
in a tight cluster on the page center). Default: no injection.
"""
import asyncio, subprocess, time, os, glob, sys, uuid
from playwright.async_api import async_playwright

PORT = 9350
CHROME = "/usr/bin/google-chrome"
if not os.path.exists(CHROME):
    cands = glob.glob("/home/huxley/.cache/ms-playwright/chromium_headless_shell-*/chrome-headless-shell-linux64/chrome-headless-shell")
    if cands: CHROME = cands[0]
print(f"Using CHROME: {CHROME}")


async def run(p, use_cdp, url, button_id, wait_ms, inject):
    if use_cdp:
        udir = f"/tmp/test4_{uuid.uuid4().hex[:8]}"
        proc = subprocess.Popen([CHROME, "--headless=new", "--no-sandbox", "--disable-gpu",
                                  f"--remote-debugging-port={PORT}", f"--user-data-dir={udir}",
                                  "about:blank"],
                                 stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        time.sleep(3)
        try:
            browser = await p.chromium.connect_over_cdp(f"http://localhost:{PORT}")
            ctx = browser.contexts[0] if browser.contexts else await browser.new_context()
            page = ctx.pages[0] if ctx.pages else await ctx.new_page()
        except Exception as e:
            proc.terminate()
            return {"error": f"CDP connect: {e}"}
    else:
        browser = await p.chromium.launch(headless=True, args=["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"])
        page = await (await browser.new_context()).new_page()

    try:
        await page.goto(url, wait_until="networkidle", timeout=15000)
        # Optionally inject page-level counter for mousemove capture
        if inject and inject.startswith("mouse:"):
            await page.evaluate("""() => {
                window.__dbgCount = 0;
                document.addEventListener('mousemove', () => { window.__dbgCount++; });
            }""")
        await page.click(f"#{button_id}", timeout=3000)
        # If mouse injection requested, do it after the start button
        if inject and inject.startswith("mouse:"):
            count = int(inject.split(":")[1])
            # Get center of viewport
            center = await page.evaluate("() => ({ w: window.innerWidth, h: window.innerHeight })")
            for i in range(count):
                x = center['w']/2 + (i * 3) % 60 - 30
                y = center['h']/2 + (i * 2) % 40 - 20
                await page.mouse.move(x, y)
                await page.wait_for_timeout(40)
            if wait_ms < 1000: wait_ms = 1000
        # Two-button mode: if there's a separate analyze button, click it after the wait
        # Heuristic: any of these is the analyze button after a collect
        for analyze_id in ["analyze-btn", "analyze", "run-analyze"]:
            try:
                # Only click if it's enabled
                disabled = await page.evaluate(f"() => document.getElementById('{analyze_id}')?.disabled")
                if disabled is False:
                    await page.click(f"#{analyze_id}", timeout=1000)
                    await page.wait_for_timeout(2000)
                    break
            except: pass
        await page.wait_for_timeout(wait_ms)
        text = await page.inner_text("body")
        # Pull debug count if available
        dbg = await page.evaluate("() => window.__dbgCount || null")
        await browser.close()
    finally:
        if use_cdp:
            proc.terminate()
            time.sleep(1)
            if proc.poll() is None: proc.kill()
    return {"body": text, "dbg": dbg}


def findings(text):
    out = []
    for line in text.split("\n"):
        line = line.strip()
        if any(k in line.lower() for k in ["verdict", "bot", "human", "score:", "detected", "suspicious", "passed", "failed", "flag"]):
            if 5 < len(line) < 200: out.append(line)
    return out


async def main():
    if len(sys.argv) < 4:
        print(__doc__); sys.exit(1)
    spike = sys.argv[1]
    button_id = sys.argv[2]
    wait_ms = int(sys.argv[3])
    inject = sys.argv[4] if len(sys.argv) > 4 else None
    url = f"http://127.0.0.1:3457/spikes/{spike}/demo.html"

    print(f"=== {spike} ===  button=#{button_id}  wait={wait_ms}ms  inject={inject or 'none'}")
    async with async_playwright() as p:
        a = await run(p, False, url, button_id, wait_ms, inject)  # vanilla, no input
        b = await run(p, True,  url, button_id, wait_ms, inject)  # CDP, no input
        c = await run(p, False, url, button_id, wait_ms, inject)  # vanilla, with input
        d = await run(p, True,  url, button_id, wait_ms, inject)  # CDP, with input

    for label, r in [("A vanilla noinput", a), ("B CDP noinput", b),
                      ("C vanilla input", c), ("D CDP input", d)]:
        if "error" in r:
            print(f"  {label}: ERROR {r['error']}")
            continue
        if r.get("dbg") is not None:
            print(f"  {label}: dbg={r['dbg']}")
        fl = findings(r['body'])
        if fl:
            for f in fl[:3]: print(f"    {f}")

    print()


asyncio.run(main())
