#!/usr/bin/env python3
"""RC111 pass player real-DOM QA.

Renders actual preview.html with inlined CSS/JS via Playwright page.set_content().
Exercises the existing app.js player lifecycle while verifying the new RC111
pass-player helper/class/data-state contract.
"""
from __future__ import annotations

import asyncio
import base64
import json
import mimetypes
import os
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "qa_screenshots"
VIEWPORTS = [
    ("mobile", 390, 844),
    ("ipad", 820, 1180),
    ("desktop", 1440, 1024),
]


def _read_asset(src: str) -> str:
    clean = src.split("?", 1)[0].split("#", 1)[0]
    return (ROOT / clean).read_text(encoding="utf-8")


def build_inlined_preview() -> str:
    html = (ROOT / "preview.html").read_text(encoding="utf-8")
    base = f'<base href="file://{ROOT.as_posix()}/">'
    qa_head = """
<script>
(function(){
  try{ history.replaceState = function(){}; history.pushState = function(){}; }catch(e){}
  const nativeFetch = window.fetch ? window.fetch.bind(window) : null;
  window.fetch = function(url, options){
    const s = String(url || '');
    if(s.includes('ota_youtube_proxy.php')){
      return Promise.resolve({ ok:true, json:function(){ return Promise.resolve({ embedUrl:'https://www.youtube.com/embed/TESTID12345?rel=0&modestbranding=1', watchUrl:'https://www.youtube.com/watch?v=TESTID12345', youtubeId:'TESTID12345' }); } });
    }
    if(s.includes('video_backup_api.php')){
      return Promise.resolve({ ok:true, json:function(){ return Promise.resolve({ status:'missing', exists:false }); } });
    }
    return nativeFetch ? nativeFetch(url, options) : Promise.reject(new Error('fetch disabled in RC111 QA'));
  };
})();
</script>
"""
    html = html.replace("<head>", f"<head>{base}\n{qa_head}", 1)

    def repl_link(match: re.Match[str]) -> str:
        attrs = match.group(1)
        href_match = re.search(r'href=["\']([^"\']+)["\']', attrs)
        if not href_match:
            return match.group(0)
        href = href_match.group(1)
        css = _read_asset(href)
        return f"<style data-inline-src=\"{href}\">\n{css}\n</style>"

    html = re.sub(r'<link\s+([^>]*rel=["\']stylesheet["\'][^>]*)>', repl_link, html)

    def repl_script(match: re.Match[str]) -> str:
        attrs = match.group(1)
        src_match = re.search(r'src=["\']([^"\']+)["\']', attrs)
        if not src_match:
            return match.group(0)
        src = src_match.group(1)
        js = _read_asset(src)
        return f"<script data-inline-src=\"{src}\">\n{js}\n</script>"

    html = re.sub(r'<script\s+([^>]*src=["\'][^"\']+["\'][^>]*)>\s*</script>', repl_script, html)
    return html


def as_data_uri(src: str) -> tuple[str, str] | None:
    if not src or src.startswith(("data:", "http://", "https://", "blob:")):
        return None
    clean = src.split("?", 1)[0].split("#", 1)[0]
    if clean.startswith("file://"):
        path = Path(clean[7:])
    else:
        path = ROOT / clean.lstrip("/")
    if not path.exists() or not path.is_file():
        return None
    mime = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
    data = base64.b64encode(path.read_bytes()).decode("ascii")
    return clean, f"data:{mime};base64,{data}"


async def inline_dom_images(page):
    imgs = await page.evaluate("""
      () => Array.from(document.images).map((img, i) => ({ i, src: img.getAttribute('src') || '' }))
    """)
    for img in imgs:
        converted = as_data_uri(img["src"])
        if not converted:
            continue
        original, data_uri = converted
        await page.evaluate("""
          ({ index, original, dataUri }) => {
            const img = document.images[index];
            if(!img) return;
            img.setAttribute('data-qa-original-src', original);
            img.src = dataUri;
          }
        """, {"index": img["i"], "original": original, "dataUri": data_uri})


async def setup_page(browser, html: str, width: int, height: int):
    ctx = await browser.new_context(viewport={"width": width, "height": height}, device_scale_factor=1)

    async def _route(route):
        url = route.request.url
        if url.startswith(("http://", "https://")):
            await route.abort()
        else:
            await route.continue_()

    await ctx.route("**/*", _route)
    page = await ctx.new_page()
    page.set_default_timeout(15000)
    page.set_default_navigation_timeout(15000)
    await page.set_content(html, wait_until="domcontentloaded")
    return ctx, page


async def set_stage2_day1(page):
    await page.evaluate("""
      () => {
        state.stage='Stage 2';
        state.phase='Phase 1';
        state.week='1';
        state.day='Day 1';
        state.current=0;
        state.inRest=false;
        state.restNextIndex=null;
        state.projectorNoYoutube=false;
      }
    """)


async def render_start(page):
    await set_stage2_day1(page)
    await page.evaluate("""
      () => { if(typeof startWorkout === 'function') startWorkout(); }
    """)
    await page.wait_for_timeout(850)
    await inline_dom_images(page)
    await page.wait_for_timeout(150)


async def render_youtube(page):
    await set_stage2_day1(page)
    await page.evaluate("""
      () => { if(typeof startWorkout === 'function') startWorkout(); }
    """)
    await page.wait_for_timeout(450)
    await page.evaluate("""
      () => { if(typeof primeWorkoutStart === 'function') primeWorkoutStart(); }
    """)
    await page.wait_for_timeout(850)
    await inline_dom_images(page)
    await page.wait_for_timeout(150)


async def render_rest(page):
    await set_stage2_day1(page)
    await page.evaluate("""
      () => { if(typeof startWorkout === 'function') startWorkout(); }
    """)
    await page.wait_for_timeout(450)
    await page.evaluate("""
      () => { if(typeof renderRest === 'function') renderRest(30, 1); }
    """)
    await page.wait_for_timeout(700)
    await inline_dom_images(page)
    await page.wait_for_timeout(150)


async def capture(page, stem: str, name: str, width: int, height: int):
    out = OUT / f"rc111_{stem}_{name}_{width}x{height}.png"
    await page.screenshot(path=str(out), full_page=False)
    return f"qa_screenshots/{out.name}"


async def metrics(page):
    return await page.evaluate("""
      () => {
        const rect = el => el ? JSON.parse(JSON.stringify(el.getBoundingClientRect())) : null;
        const screen = document.getElementById('exerciseScreen');
        const wrap = document.getElementById('videoWrap');
        return {
          screen: document.body.dataset.screen || '',
          bodyExerciseActive: document.body.classList.contains('exercise-active'),
          helperLoaded: !!window.NEXTRenderers?.passPlayer,
          passScreenClass: screen?.className || '',
          passMode: screen?.dataset.passMode || '',
          videoSource: screen?.dataset.videoSource || '',
          wrapClass: wrap?.className || '',
          wrapPassMode: wrap?.dataset.passMode || '',
          wrapVideoSource: wrap?.dataset.videoSource || '',
          hasStartStage: !!document.querySelector('.next-pass-start-stage.start-stage'),
          hasRestStage: !!document.querySelector('.next-pass-rest-stage.rest-stage'),
          hasFallback: !!document.querySelector('.next-pass-fallback'),
          hasIframe: !!document.querySelector('#videoWrap iframe'),
          hasVideo: !!document.querySelector('#videoWrap video'),
          mediaRect: rect(document.querySelector('.next-pass-media-card')),
          infoRect: rect(document.querySelector('.next-pass-info-card')),
          controlsRect: rect(document.querySelector('.next-pass-controls')),
          exerciseName: document.getElementById('exerciseName')?.textContent?.trim() || '',
          nextText: document.getElementById('nextBtn')?.textContent?.trim() || '',
          brokenImages: Array.from(document.images).filter(img => img.naturalWidth === 0 && !String(img.src || '').startsWith('http')).map(img => img.getAttribute('data-qa-original-src') || img.getAttribute('src'))
        };
      }
    """)


async def main():
    from playwright.async_api import async_playwright
    OUT.mkdir(exist_ok=True)
    html = build_inlined_preview()
    results = {"method":"Chromium Playwright page.set_content with inlined preview.html/CSS/JS; external iframes/network aborted except mocked local proxy fetches.", "start":[], "youtube":[], "rest":[]}
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            executable_path="/usr/bin/chromium",
            headless=True,
            args=["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-gpu-sandbox", "--use-gl=swiftshader", "--enable-unsafe-swiftshader"],
        )
        for name, w, h in VIEWPORTS:
            for state_name, renderer in (("start", render_start), ("youtube", render_youtube), ("rest", render_rest)):
                ctx, page = await setup_page(browser, html, w, h)
                await renderer(page)
                m = await metrics(page)
                m["screenshot"] = await capture(page, f"pass_player_{state_name}", name, w, h)
                results[state_name].append(m)
                await ctx.close()
        await browser.close()
    (OUT / "rc111_pass_player_dom_metrics.json").write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(results, ensure_ascii=False, indent=2), flush=True)


if __name__ == "__main__":
    asyncio.run(main())
    os._exit(0)
