#!/usr/bin/env python3
from __future__ import annotations
import asyncio, base64, importlib.util, json, mimetypes
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
OUT=ROOT/'qa_screenshots'; OUT.mkdir(exist_ok=True)
spec=importlib.util.spec_from_file_location('rc18qa', str(ROOT/'tests/rc18_pose_motion_runtime_safari_scale_real_dom_qa.py'))
rc18qa=importlib.util.module_from_spec(spec); spec.loader.exec_module(rc18qa)

def data_uri(src: str):
    if not src or src.startswith(('data:','http')): return None
    clean=src.split('?',1)[0].split('#',1)[0]
    p=ROOT/clean.lstrip('/')
    if not p.exists() or not p.is_file(): return None
    mime=mimetypes.guess_type(p.name)[0] or 'application/octet-stream'
    return clean, 'data:%s;base64,%s'%(mime,base64.b64encode(p.read_bytes()).decode('ascii'))

async def inline_all_images(page):
    imgs=await page.evaluate("""() => Array.from(document.images).map((img,i)=>({i,src:img.getAttribute('src')||''}))""")
    for im in imgs:
        d=data_uri(im['src'])
        if not d: continue
        clean,uri=d
        await page.evaluate("""({i,clean,uri})=>{const img=document.images[i]; if(img){img.dataset.qaOriginalSrc=clean; img.src=uri;}}""", {'i':im['i'],'clean':clean,'uri':uri})
    await page.wait_for_timeout(120)

async def boot(page,width,height):
    await page.set_viewport_size({'width':width,'height':height})
    await page.set_content(rc18qa.inline_preview(), wait_until='domcontentloaded')
    await page.wait_for_function("() => window.showProgram && window.chooseProgram && window.showImageAdmin && window.activeProgramId", timeout=10000)

async def verify_program(page,width,height,name):
    await boot(page,width,height)
    await page.evaluate("""() => { window.showProgram(); }""")
    await page.wait_for_timeout(300)
    buttons=await page.evaluate("""() => Array.from(document.querySelectorAll('#programCatalog [data-next-action="choose-program"]')).map(b=>b.dataset.programId)""")
    if 'program-stage-2' not in buttons: raise AssertionError('Program Stage 2 choose button missing')
    await page.locator('#programCatalog [data-next-action="choose-program"][data-program-id="program-stage-2"]').first.click(timeout=5000)
    await page.wait_for_timeout(350)
    selected=await page.evaluate("""() => ({active:window.activeProgramId(), rows:window.currentRows().slice(0,2).map(r=>r.stage), selected:document.querySelector('#programCatalog .next-program-card.selected')?.textContent.trim()||''})""")
    if selected['active']!='program-stage-2': raise AssertionError(f'Normal program click did not activate Stage 2: {selected}')
    if not all(s=='Stage 2' for s in selected['rows']): raise AssertionError(f'Workout rows not aligned with selected program: {selected}')

    # Reproduce the old broken path: switching after an active program has progress.
    # RC22 set prompt state but rendered no visible prompt; RC23 must expose confirm controls.
    await page.evaluate("""() => { window.showProgram(); window.programProgress=(p)=>p.id===window.activeProgramId()?{done:1,total:24,started:true,complete:false,all:[]}:{done:0,total:24,started:false,complete:false,all:[]}; }""")
    await page.wait_for_timeout(200)
    await page.locator('#programCatalog [data-next-action="choose-program"][data-program-id="program-stage-1"]').first.click(timeout=5000)
    await page.wait_for_timeout(350)
    prompt=await page.evaluate("""() => ({visible:!!document.querySelector('.program59-inline-panel'), text:document.querySelector('.program59-inline-panel')?.textContent.trim()||''})""")
    if not prompt['visible'] or 'Byta program' not in prompt['text']: raise AssertionError(f'Switch prompt not visible: {prompt}')
    await page.locator('.program59-inline-panel [data-next-action="confirm-program-switch"]').first.click(timeout=5000)
    await page.wait_for_timeout(350)
    after=await page.evaluate("""() => ({active:window.activeProgramId(), rows:window.currentRows().slice(0,2).map(r=>r.stage)})""")
    if after['active']!='program-stage-1': raise AssertionError(f'Confirmed switch did not activate Stage 1: {after}')
    if not all(s=='Stage 1' for s in after['rows']): raise AssertionError(f'Confirmed switch rows not aligned: {after}')
    await inline_all_images(page)
    await page.screenshot(path=str(OUT/f'rc23_program_switch_{name}_{width}x{height}.png'), full_page=False)
    return {'selected':selected,'prompt':prompt,'after':after}

async def verify_pose_slots(page):
    await boot(page,1440,1024)
    await page.evaluate("""() => { window.showImageAdmin(); }""")
    await page.wait_for_timeout(800)
    await page.evaluate("""() => { const row=Array.from(document.querySelectorAll('.next-image-admin-row')).find(r=>/Enbensrodd|Single Arm Row/i.test(r.textContent)); if(row) row.click(); }""")
    await page.wait_for_timeout(350)
    data=await page.evaluate("""() => ({
      bg:document.querySelector('#imageAdminPoseBuilder .next-exercise-visual-bg')?.getAttribute('src')||'',
      imgs:Array.from(document.querySelectorAll('#imageAdminPoseBuilder .next-pose-preview-img')).map(img=>({key:img.dataset.poseKey,style:img.getAttribute('style')||''}))
    })""")
    if data['bg'].endswith('-real.webp'): raise AssertionError(f'Runtime theme is using generated real background with baked athlete: {data["bg"]}')
    if not data['imgs']: raise AssertionError('Pose preview images missing')
    if any('--pose-slot:0' in x['style'] for x in data['imgs']): raise AssertionError(f'Pose slot collapsed to zero: {data}')
    if not any('--pose-slot:2' in x['style'] for x in data['imgs']): raise AssertionError(f'Expected centered slot distribution missing: {data}')
    await inline_all_images(page)
    await page.screenshot(path=str(OUT/'rc23_image_admin_pose_slots_desktop_1440x1024.png'), full_page=False)
    return data

async def main():
    from playwright.async_api import async_playwright
    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'])
        results={}
        for width,height,name in [(390,844,'mobile'),(820,1180,'ipad'),(1440,1024,'desktop')]:
            page=await browser.new_page()
            results[f'program_{name}']=await verify_program(page,width,height,name)
            await page.close()
        page=await browser.new_page()
        results['pose_slots']=await verify_pose_slots(page)
        await page.close()
        await browser.close()
    (OUT/'rc23_program_switch_prompt_pose_slot_metrics.json').write_text(json.dumps(results,indent=2,ensure_ascii=False),encoding='utf-8')
    print('RC23 program switch prompt + pose slot QA OK')

if __name__=='__main__':
    asyncio.run(main())
