# -*- coding: utf-8 -*-
"""分片版组卷台 E2E：分片齐全、懒加载配图回填、预览/导出拿到真图、分片断网有提示不崩。"""
import urllib.parse, time, os, sys
from playwright.sync_api import sync_playwright

URL = 'http://127.0.0.1:8848/' + urllib.parse.quote('组卷台.html')
res = []
def ok(name, cond, extra=''):
    res.append((name, bool(cond))); print('%s %s %s' % ('✓' if cond else '✗', name, extra))

def wait_fill(pg, sel='#list', t=15):
    """等到容器里没有 data-fig 占位（或超时），返回剩余占位数"""
    for _ in range(t * 4):
        n = pg.evaluate("(s)=>document.querySelectorAll(s+' img[data-fig]').length", sel)
        if n == 0: return 0
        time.sleep(0.25)
    return n

with sync_playwright() as p:
    b = p.chromium.launch(headless=True)
    ctx = b.new_context(viewport={'width': 1440, 'height': 950}, accept_downloads=True)
    pg = ctx.new_page(); errs = []; reqs = []
    pg.on('pageerror', lambda e: errs.append(str(e)[:150]))
    pg.on('response', lambda r: reqs.append((r.url, r.status, r.headers.get('content-length'))))
    t0 = time.time(); pg.goto(URL, timeout=600000, wait_until='domcontentloaded'); pg.wait_for_selector('#bCompose', timeout=600000); t1 = time.time() - t0
    time.sleep(2)
    st = pg.evaluate("({files:ZJ._files,want:ZJ._want,data:DATA.length,figm:Object.keys(FIGM).length,fig:Object.keys(FIG).length,verbar:!!document.getElementById('verbar')})")
    ok('分片齐全 files==want 且无横幅', st['files'] == st['want'] and not st['verbar'], str(st))
    ok('DATA 就绪 >150k', st['data'] > 150000, str(st['data']))
    ok('FIGM 就绪 >80k、FIG 启动为空', st['figm'] > 80000 and st['fig'] == 0, 'figm=%d fig=%d' % (st['figm'], st['fig']))
    tr = sum(int(x[2] or 0) for x in reqs if '/zj_assets/' in x[0] or x[0].endswith('.html'))
    print('   首屏 %.1fs，启动请求 %d 个，传输 %.1f MB（含 html+分片+vendor）' % (t1, len(reqs), tr / 1e6))
    ok('启动不拉 fig 分片', not any('/zj_assets/fig/' in x[0] for x in reqs))

    # 题库列表：胡小群带图 / 中考内联公式 / AMC8 图选项 → 占位全部回填成 data:
    cases = [('胡小群课程', None, None), ('上海中考真题', '2024年', None), ('AMC8真题', '2006年', None), ('全国中考真题', '江苏', None)]
    for tb, gg, _ in cases:
        pg.select_option('#ftb', tb); time.sleep(0.3)
        if gg is None: gg = pg.evaluate("document.querySelectorAll('#fg option')[1].value")
        pg.select_option('#fg', gg); time.sleep(0.4)
        sec = pg.evaluate("[...document.querySelectorAll('#fsec option')].map(o=>o.value).filter(Boolean).find(v=>true)")
        pg.select_option('#fsec', sec); time.sleep(1.5)
        left = wait_fill(pg)
        r = pg.evaluate("""(()=>{const im=[...document.querySelectorAll('#list .q img')];
          return {imgs:im.length,data:im.filter(i=>i.src.startsWith('data:image')&&!i.src.startsWith('data:image/gif;base64,R0lGOD')).length,
                  need:filtered.slice(0,50).reduce((a,d)=>a+figKeysOf(d).length,0),inl:document.querySelectorAll('#list img.inl').length,blk:document.querySelectorAll('#list img.blk').length};})()""")
        ok('%s/%s 图回填 need=%d' % (tb, gg, r['need']), left == 0 and r['data'] == r['need'], str(r))
    ok('渲染后 FIG 只装了部分分片', pg.evaluate("Object.keys(FIG).length") < st['figm'] * 0.5, 'FIG=%d' % pg.evaluate("Object.keys(FIG).length"))

    # 试卷篮 3 道带图题（胡小群 f、中考 asset、AMC8）→ 预览有真图
    ids = pg.evaluate("""(()=>{const pick=(f)=>DATA.find(f).id;return [pick(d=>d.tb==='胡小群课程'&&d.f&&FIGM[d.f]),pick(d=>d.tb==='上海中考真题'&&d.im&&d.im.length),pick(d=>d.tb==='AMC8真题'&&d.im&&d.im.length)];})()""")
    pg.evaluate("(ids)=>{cart=ids.slice();renderCart();}", ids)
    pg.click('#bPrev'); time.sleep(4)
    r = pg.evaluate("""(()=>{const im=[...document.querySelectorAll('#paper img')];return {n:im.length,data:im.filter(i=>i.src.startsWith('data:image')&&!i.src.startsWith('data:image/gif;base64,R0lGOD')).length,ph:document.querySelectorAll('#paper img[data-fig]').length};})()""")
    ok('预览 3 道带图题图为真图', r['n'] >= 3 and r['data'] == r['n'] and r['ph'] == 0, str(r))
    pg.evaluate("document.getElementById('bClose').click()"); time.sleep(0.3)

    # 本地 docx 兜底导出能产出文件（含 ImageRun 需要真字节）
    try:
        with pg.expect_download(timeout=60000) as dl:
            pg.evaluate("doDocxLocal()")
        path = dl.value.path(); sz = os.path.getsize(path)
        ok('本地 docx 导出成功 >20KB', sz > 20000, '%d B' % sz)
    except Exception as e:
        ok('本地 docx 导出成功', False, str(e)[:100])

    # 分片断网：占位显示失败提示，页面不崩
    pg.route('**/zj_assets/fig/**', lambda route: route.abort())
    pg.select_option('#ftb', '初中培优讲义'); time.sleep(0.3)
    gg = pg.evaluate("[...document.querySelectorAll('#fg option')].map(o=>o.value).find(v=>v==='九下')")
    pg.select_option('#fg', gg); time.sleep(0.4)
    sec = pg.evaluate("[...document.querySelectorAll('#fsec option')].map(o=>o.value).filter(Boolean)[3]")
    pg.select_option('#fsec', sec)
    for _ in range(80):   # 最多 20s：占位要么回填完、要么被标成失败
        time.sleep(0.25)
        r = pg.evaluate("({err:document.querySelectorAll('#list img[data-figerr]').length,ph:document.querySelectorAll('#list img[data-fig]').length})")
        if r['err'] > 0 or r['ph'] == 0: break
    ok('分片失败有「图加载失败」标记且不崩', (r['err'] > 0 or r['ph'] == 0), str(r))
    pg.unroute('**/zj_assets/fig/**')
    ok('无 JS 错误', not errs, '; '.join(errs[:2]))
    b.close()

bad = [r for r in res if not r[1]]
print('\n%d/%d 通过' % (len(res) - len(bad), len(res)))
sys.exit(1 if bad else 0)
