#!/usr/bin/env python3
"""ARRFLIX headless smoke-test. Logs in via API, navigates to a detail page,
captures screenshot + console errors + network failures + computed-style for
backdrop. Pass dev or prod URL as argv[1]."""
import sys, json, time, os, asyncio, urllib.request, urllib.error
from playwright.async_api import async_playwright

URL  = sys.argv[1] if len(sys.argv) > 1 else "https://dev.arrflix.s8n.ru"
USER = sys.argv[2] if len(sys.argv) > 2 else "USER-F-mirror"
PASS = sys.argv[3] if len(sys.argv) > 3 else "dev-test-USER-F"
ITEM = sys.argv[4] if len(sys.argv) > 4 else None  # auto-pick first Series if absent
OUT  = sys.argv[5] if len(sys.argv) > 5 else "/tmp/arrflix-headless"
os.makedirs(OUT, exist_ok=True)

DEVICE = "headless-test"
DEVICE_ID = "headless-test-2026-05-09"
CLIENT = "Headless"
VERSION = "1.0"

def auth_header(token=None):
    h = (f'MediaBrowser Client="{CLIENT}", Device="{DEVICE}", '
         f'DeviceId="{DEVICE_ID}", Version="{VERSION}"')
    if token:
        h += f', Token="{token}"'
    return h

def api_post(path, body, token=None):
    req = urllib.request.Request(
        f"{URL}{path}",
        data=json.dumps(body).encode(),
        headers={
            "Authorization": auth_header(token),
            "Content-Type": "application/json",
        },
        method="POST",
    )
    ctx = __import__("ssl")._create_unverified_context()
    with urllib.request.urlopen(req, context=ctx) as r:
        return json.loads(r.read())

def api_get(path, token=None):
    req = urllib.request.Request(
        f"{URL}{path}",
        headers={"Authorization": auth_header(token)},
    )
    ctx = __import__("ssl")._create_unverified_context()
    with urllib.request.urlopen(req, context=ctx) as r:
        return json.loads(r.read())

def login():
    r = api_post("/Users/AuthenticateByName",
                 {"Username": USER, "Pw": PASS})
    return r["AccessToken"], r["User"]["Id"], r["ServerId"]

async def main():
    token, user_id, server_id = login()
    print(f"[+] Authenticated as {USER} ({user_id})")

    item_id = ITEM
    if not item_id:
        items = api_get(
            f"/Users/{user_id}/Items?Recursive=true&IncludeItemTypes=Series&Limit=5",
            token)
        if items.get("Items"):
            item_id = items["Items"][0]["Id"]
            print(f"[+] Auto-picked Series: {items['Items'][0]['Name']} ({item_id})")
        else:
            print("[!] No series found, falling back to root")

    console_messages = []
    network_failures = []

    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True,
            args=["--no-sandbox", "--disable-dev-shm-usage"])
        ctx = await browser.new_context(
            viewport={"width": 1600, "height": 900},
            ignore_https_errors=True)
        page = await ctx.new_page()

        page.on("console", lambda m: console_messages.append(
            f"[{m.type}] {m.text}"))
        page.on("requestfailed", lambda r: network_failures.append(
            f"{r.method} {r.url} :: {r.failure}"))
        page.on("response", lambda r: None if r.status < 400 else
            network_failures.append(f"HTTP {r.status} {r.url}"))

        # Auth via login form
        await page.goto(f"{URL}/web/", wait_until="networkidle", timeout=30000)
        await asyncio.sleep(3)
        # Wait for any input rendered by SPA
        try:
            await page.wait_for_selector("input", timeout=20000)
            inputs = await page.evaluate(
                "() => Array.from(document.querySelectorAll('input')).map(i => ({id:i.id, name:i.name, type:i.type, placeholder:i.placeholder}))")
            print(f"[*] inputs: {inputs}")
            # Find username input by heuristic
            user_sel = None
            pass_sel = None
            for i in inputs:
                fid, fname, ftype = i.get('id',''), i.get('name',''), i.get('type','')
                if not user_sel and (ftype == 'text' or 'user' in (fid+fname).lower() or 'name' in (fid+fname).lower()):
                    user_sel = f'#{fid}' if fid else f'input[name="{fname}"]'
                if not pass_sel and ftype == 'password':
                    pass_sel = f'#{fid}' if fid else f'input[name="{fname}"]'
            print(f"[*] user_sel={user_sel} pass_sel={pass_sel}")
            if user_sel and pass_sel:
                await page.fill(user_sel, USER)
                await page.fill(pass_sel, PASS)
                await page.keyboard.press("Enter")
                await page.wait_for_load_state("networkidle", timeout=20000)
                await asyncio.sleep(2)
                print("[+] logged in via form")
            else:
                print("[!] could not locate login fields")
        except Exception as e:
            print(f"[!] form login failed: {e}")

        # Navigate to detail page
        target = (f"{URL}/web/#/details?id={item_id}&serverId={server_id}"
                  if item_id else f"{URL}/web/")
        print(f"[*] navigating: {target}")
        await page.goto(target, wait_until="networkidle", timeout=30000)
        await asyncio.sleep(4)  # let SPA paint backdrop

        # Probe key DOM elements (extended)
        probe = await page.evaluate("""() => {
            const result = {};
            const sel = ['.itemBackdrop', '.detailBackdrop', '.backdropContainer',
                        '.backgroundContainer', '.layout-desktop',
                        'body', '#reactRoot', '.itemDetailPage',
                        'video', '.htmlvideoplayer', '.btnPlay', '.detailPagePrimaryContainer',
                        '.detailSection', '.detailVerticalSection', '.itemsContainer',
                        '.padded-bottom-page', '.mainAnimatedPages', '.pageContainer',
                        '.cardScalable', '.scrollSlider', '.sectionTitleContainer',
                        '.detailPageContent', '.detailPageWrapperContainer'];
            for (const s of sel) {
                const els = document.querySelectorAll(s);
                if (els.length === 0) { result[s] = '<absent>'; continue; }
                const el = els[0];
                const cs = getComputedStyle(el);
                result[s] = {
                    count: els.length,
                    display: cs.display,
                    opacity: cs.opacity,
                    visibility: cs.visibility,
                    background: cs.backgroundColor,
                    backgroundImage: cs.backgroundImage.slice(0, 80),
                    zIndex: cs.zIndex,
                    rect: el.getBoundingClientRect().toJSON(),
                };
            }
            result['__title'] = document.title;
            const playBtn = document.querySelector('.btnPlay, [data-action="play"]');
            result['__playBtnText'] = playBtn ? (playBtn.innerText || playBtn.textContent || '').trim() : null;
            result['__bodyClasses'] = document.body.className;
            result['__url'] = location.href;
            return result;
        }""")

        # Two screenshots: top viewport + scrolled to mid-page (so fixed backdrop renders correctly)
        screenshot = os.path.join(OUT, f"{URL.replace('https://','').replace('.','_')}-detail.png")
        await page.screenshot(path=screenshot, full_page=False)
        # Scroll halfway down to verify pinned backdrop persists
        await page.evaluate("() => window.scrollTo(0, document.body.scrollHeight * 0.5)")
        await asyncio.sleep(1)
        scrolled = os.path.join(OUT, f"{URL.replace('https://','').replace('.','_')}-scrolled.png")
        await page.screenshot(path=scrolled, full_page=False)
        print(f"[+] screenshot: {screenshot}")

        with open(os.path.join(OUT, "probe.json"), "w") as f:
            json.dump({
                "url": URL,
                "user": USER,
                "item": item_id,
                "probe": probe,
                "console": console_messages[-50:],
                "network_failures": network_failures[-50:],
            }, f, indent=2)
        print(f"[+] probe.json: {os.path.join(OUT, 'probe.json')}")
        print(f"[+] console msgs: {len(console_messages)}")
        print(f"[+] network failures: {len(network_failures)}")

        await browser.close()

asyncio.run(main())
