# -*- coding: utf-8 -*-
"""袋鼠真题并库验收（真浏览器）：教材下拉有「袋鼠真题」；年份轴倒序；同年 A/B 两节；
   随机各抽一张 A/B 卡（一张含「选项见图」、一张有 qe）看渲染：图数 == 需求数、无占位、无露 markdown/asset://、零 JS 错误。"""
import urllib.parse, time, sys, random
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))

with sync_playwright() as p:
    b = p.chromium.launch(headless=True); pg = b.new_page(viewport={'width': 1400, 'height': 950})
    errs = []; pg.on('pageerror', lambda e: errs.append(str(e)[:150]))
    pg.goto(URL, timeout=600000, wait_until='domcontentloaded'); pg.wait_for_selector('#bCompose', timeout=600000); time.sleep(1.5)
    st = pg.evaluate("""(()=>{const tbs=[...document.querySelectorAll('#ftb option')].map(o=>o.value).filter(Boolean);
      const mk=DATA.filter(d=>d.tb==='袋鼠真题');const ggs=Object.keys(COURSE['袋鼠真题']||{});
      const nodes=Object.values(COURSE['袋鼠真题']||{}).reduce((a,v)=>a+v.length,0);
      return {tbs:tbs.length,hasMk:tbs.includes('袋鼠真题'),n:mk.length,total:DATA.length,ggs,nodes,
              a:mk.filter(d=>d.cd.includes('Level A')).length,b:mk.filter(d=>d.cd.includes('Level B')).length,
              qe:mk.filter(d=>d.qe).length,withFig:mk.filter(d=>d.f||(d.mf&&d.mf.length)).length,
              dd:mk.reduce((o,d)=>{o[d.d]=(o[d.d]||0)+1;return o},{}),
              firstNodes:(COURSE['袋鼠真题'][ggs[0]]||[]).map(x=>x[1])};})()""")
    ok('教材下拉含 袋鼠真题', st['hasMk'], 'tbs=%d' % st['tbs'])
    ok('题数 A 340 / B 480 / 全库 161344', st['a'] == 340 and st['b'] == 480 and st['total'] == 161344, str({k: st[k] for k in ('a', 'b', 'total')}))
    ok('年份轴倒序且节点 37', st['ggs'][0] == '2026年' and st['ggs'] == sorted(st['ggs'], reverse=True) and st['nodes'] == 37, '%s… nodes=%d 首年节=%s' % (st['ggs'][:3], st['nodes'], st['firstNodes']))
    ok('难度 2/3/4 分档齐全、qe 有存', all(str(k) in map(str, st['dd']) for k in (2, 3, 4)) and st['qe'] >= 600, 'd=%s qe=%d withFig=%d' % (st['dd'], st['qe'], st['withFig']))

    # 抽卡：一张含「选项见图」的 A 卡、一张有 qe 的 B 卡
    picks = pg.evaluate("""(()=>{const mk=DATA.filter(d=>d.tb==='袋鼠真题');
      const a=mk.find(d=>d.cd.includes('Level A')&&/选项见图/.test(d.q))||mk.find(d=>d.cd.includes('Level A')&&d.f);
      const b=mk.find(d=>d.cd.includes('Level B')&&d.qe&&(d.f||(d.mf&&d.mf.length)))||mk.find(d=>d.cd.includes('Level B')&&d.qe);
      return [a,b].map(d=>({id:d.id,gg:d.gg,cd:d.cd,need:figKeysOf(d).length,q:d.q.slice(0,60)}));})()""")
    for i, pk in enumerate(picks):
        pg.select_option('#ftb', '袋鼠真题'); time.sleep(0.3); pg.select_option('#fg', pk['gg']); time.sleep(0.4)
        pg.select_option('#fsec', pk['cd']); time.sleep(2.5)
        for _ in range(40):
            if pg.evaluate("document.querySelectorAll('#list img[data-fig]').length") == 0: break
            time.sleep(0.25)
        r = pg.evaluate("""(id)=>{const c=[...document.querySelectorAll('#list .q')].find(e=>e.innerText.includes(id));if(!c)return null;
          const im=[...c.querySelectorAll('img')];return {found:true,imgs:im.length,data:im.filter(i=>/^(data:image|https?:)/.test(i.src)&&!i.src.startsWith('data:image/gif;base64,R0lGOD')).length,
            ph:c.querySelectorAll('img[data-fig]').length,leak:/asset:\\/\\/|!\\[|⟦/.test(c.innerText),mj:c.querySelectorAll('mjx-container').length,text:c.innerText.replace(/\\s+/g,' ').slice(0,140)};}""", pk['id'])
        ok('%s 卡渲染（%s）图 %d/%d 无占位无露源码' % (pk['id'], pk['cd'], (r or {}).get('data', -1), pk['need']),
           r and r['found'] and r['data'] == pk['need'] and r['ph'] == 0 and not r['leak'], (r or {}).get('text', 'not found'))
        c = pg.query_selector('#list .q:has-text("%s")' % pk['id'])
        if c: c.screenshot(path='/private/tmp/claude-501/-Users-luyan/bd181c5c-f1b3-408e-b01f-b6a489cc2577/scratchpad/mk_card_%d.png' % i)
    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)
