#!/usr/bin/env python3
"""RC110 pass page real-DOM QA.

Renders the actual preview.html with inlined CSS/JS via Playwright page.set_content().
This keeps the real runtime DOM, renderer functions and CSS cascade while avoiding
sandbox-blocked file/http navigation.
"""
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' }); } });
    }
    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 RC110 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(12000)
    page.set_default_navigation_timeout(12000)
    await page.set_content(html, wait_until="domcontentloaded")
    return ctx, page


async def render_overview(page):
    await page.evaluate("""
      () => {
        if(typeof renderOverview === 'function') renderOverview();
        if(typeof show === 'function') show('overviewScreen');
      }
    """)
    await page.wait_for_timeout(500)
    await inline_dom_images(page)
    await page.wait_for_timeout(150)


async def render_active_pass(page):
    await page.evaluate("""
      () => {
        if(typeof startWorkout === 'function') startWorkout();
      }
    """)
    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"rc110_{stem}_{name}_{width}x{height}.png"
    await page.screenshot(path=str(out), full_page=False)
    return f"qa_screenshots/{out.name}"


async def overview_metrics(page):
    return await page.evaluate("""
      () => {
        const rect = el => el ? JSON.parse(JSON.stringify(el.getBoundingClientRect())) : null;
        const root = document.querySelector('.next-pass-overview-page');
        const hero = document.querySelector('.next-pass-overview-hero');
        const block = document.querySelector('.next-pass-overview-block');
        return {
          screen: document.body.dataset.screen || '',
          bodyExerciseActive: document.body.classList.contains('exercise-active'),
          rootRect: rect(root),
          heroRect: rect(hero),
          blockCount: document.querySelectorAll('.next-pass-overview-block').length,
          firstBlockOpen: !!block?.open,
          exerciseRows: document.querySelectorAll('.next-overview-exercise-row').length,
          legacyOvClasses: document.querySelectorAll('[class*="ov47"],[class*="ov51"],[class*="ov43"],[class*="ov45"]').length,
          actionButtonClass: document.querySelector('.next-pass-overview-actions .next-button')?.className || '',
          equipmentPanelClass: document.querySelector('.next-pass-equipment-panel')?.className || ''
        };
      }
    """)


async def active_pass_metrics(page):
    return await page.evaluate("""
      () => {
        const rect = el => el ? JSON.parse(JSON.stringify(el.getBoundingClientRect())) : null;
        return {
          screen: document.body.dataset.screen || '',
          bodyExerciseActive: document.body.classList.contains('exercise-active'),
          passScreenClass: document.getElementById('exerciseScreen')?.className || '',
          topbarClass: document.querySelector('.next-pass-topbar')?.className || '',
          layoutRect: rect(document.querySelector('.next-pass-layout')),
          mediaRect: rect(document.querySelector('.next-pass-media-card')),
          infoRect: rect(document.querySelector('.next-pass-info-card')),
          controlsRect: rect(document.querySelector('.next-pass-controls')),
          videoWrapClass: document.getElementById('videoWrap')?.className || '',
          exerciseName: document.getElementById('exerciseName')?.textContent?.trim() || '',
          nextText: document.getElementById('nextBtn')?.textContent?.trim() || '',
          hasLegacyNextClasses: !!document.querySelector('.next-pass-screen .stage-layout.next-pass-layout .next-card'),
          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; direct file/http navigation is unreliable in sandbox.", "overview":[], "activePass":[]}
    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:
            ctx, page = await setup_page(browser, html, w, h)
            await render_overview(page)
            m = await overview_metrics(page)
            m["screenshot"] = await capture(page, "pass_overview", name, w, h)
            results["overview"].append(m)
            await ctx.close()

            ctx, page = await setup_page(browser, html, w, h)
            await render_active_pass(page)
            m = await active_pass_metrics(page)
            m["screenshot"] = await capture(page, "active_pass", name, w, h)
            results["activePass"].append(m)
            await ctx.close()
        await browser.close()
    (OUT / "rc110_pass_page_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)
