#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""P2 全量验收：导航重构 + 拜访与活动融合页 + 订单 hub + 深链 + 回归。"""
import json
import sys
from playwright.sync_api import sync_playwright

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
BASE = 'http://127.0.0.1:8765/'
USER = '刘新元'
sess = json.load(open(ROOT + '/数据/auth_sessions.json', encoding='utf-8'))
mine = sorted([(v['expire_at'], k, v) for k, v in sess.items() if v.get('name') == USER], reverse=True, key=lambda x: x[0])
TOKEN, IDENT = mine[0][1], dict(mine[0][2])
IDENT['open_id'] = 'ou_preview_only'
IDENT.pop('expire_at', None)

EXPECT = {'dashboard', 'visitStats', 'orders', 'complaints', 'targetsAdmin', 'dataMgmt',
          'customers', 'groups', 'oppSandbox', 'visitPrep', 'specialReq',
          'todos', 'expense', 'aiAssistant'}
GONE = {'myActivities', 'activityEntry', 'orderEntry', 'testReport', 'training'}

ok = True
def chk(label, cond, detail=''):
    global ok
    print('  %s %-40s %s' % ('✅' if cond else '❌', label, detail))
    if not cond:
        ok = False

with sync_playwright() as p:
    br = p.chromium.launch(channel='chrome', headless=True)
    pg = br.new_page(viewport={'width': 1500, 'height': 1000})
    errs = []
    pg.on('pageerror', lambda e: errs.append(str(e)))
    pg.on('dialog', lambda d: d.accept())
    pg.goto(BASE, wait_until='domcontentloaded')
    pg.evaluate("""([tk, idn]) => {
        localStorage.setItem('bior_auth_token', tk);
        localStorage.setItem('bior_sw_user_v2', JSON.stringify(idn));
    }""", [TOKEN, IDENT])
    pg.goto(BASE + '工具/index.html', wait_until='load')
    pg.wait_for_timeout(2500)
    pg.evaluate("""() => { window.state = window.state || {};
        window.state.user = JSON.parse(JSON.stringify(%s));
        if (typeof showApp === 'function') showApp(); }""" % json.dumps(IDENT, ensure_ascii=False))
    pg.evaluate("() => { if (typeof loadData === 'function') loadData(); }")
    for _ in range(20):
        pg.wait_for_timeout(1000)
        st = pg.evaluate("() => ({l: state.loading, n: Object.keys(state.data||{}).length})")
        if st['n'] >= 8 and not st['l']:
            break

    print('① 导航重构')
    m = pg.evaluate("""() => ({ids:(window.TABS||[]).map(t=>t.id), labels:(window.TABS||[]).map(t=>t.label),
        groups:[...new Set((window.TABS||[]).map(t=>t.group))],
        nav:(document.getElementById('sidebar')||{}).innerText||'',
        keep:window.__keepProbe||null})""")
    chk('可见 Tab 集合 = 14 项预期', set(m['ids']) == EXPECT, '缺:' + str(EXPECT - set(m['ids'])) + ' 多:' + str(set(m['ids']) - EXPECT))
    chk('已下线 Tab 不在导航', not (set(m['ids']) & GONE), str(set(m['ids']) & GONE))
    chk('分组只剩 4 个域', set(m['groups']) == {'biz', 'cust', 'mine', 'ai'}, str(m['groups']))
    for g in ['经营管理', '客户管理']:
        chk('侧栏出现「%s」' % g, g in m['nav'])
    for g in ['我的活动', '活动录入', '历史成交价']:
        chk('侧栏不再出现「%s」' % g, g not in m['nav'])
    chk('底栏改造生效', pg.evaluate("() => (document.getElementById('bottomNav')||{}).innerText||''").find('拜访与活动') >= 0,
        pg.evaluate("() => ((document.getElementById('bottomNav')||{}).innerText||'').replace(/\\s+/g,' ')")[:60])

    print('\n② 拜访与活动 · 看态')
    pg.evaluate("() => { visitHubState='see'; state.tab='visitStats'; render(); }")
    pg.wait_for_timeout(3000)
    v = pg.evaluate("""() => { const b=document.getElementById('visitBody'); const T=e=>e?e.innerText.replace(/\\s+/g,' ').trim():'';
      return {txt:T(b).slice(0,150), vals:[...document.querySelectorAll('#visitBody .value')].map(T),
              hasMonth:!!document.querySelector('#main input[type=month]'),
              hasScope:!!document.querySelector('#main .todo-filters'),
              err:T(b).indexOf('加载失败')>=0}; }""")
    chk('看态渲染 KPI', len(v['vals']) >= 2, str(v['vals']))
    chk('看态无加载失败', not v['err'], v['txt'][:70])
    chk('有月份选择 + 范围切换(管理者)', v['hasMonth'] and v['hasScope'])

    print('\n③ 拜访与活动 · 录态 + 深链')
    pg.evaluate("() => visitSetState('rec')")
    pg.wait_for_timeout(2500)
    r = pg.evaluate("""() => { const f=document.querySelector('#main iframe'); return {src:f?f.getAttribute('src'):'', has:!!f}; }""")
    chk('录态内嵌录入页且版本号已升', r['has'] and '20261007p2' in (r['src'] or ''), (r['src'] or '')[:70])
    pg.evaluate("() => openActivityEntry('某某诊断科技有限公司')")
    pg.wait_for_timeout(2500)
    r2 = pg.evaluate("""() => { const f=document.querySelector('#main iframe'); return {tab:state.tab, hs:visitHubState,
        src:f?(f.getAttribute('src')||''):''}; }""")
    chk('openActivityEntry → 录态', r2['tab'] == 'visitStats' and r2['hs'] == 'rec', json.dumps(r2, ensure_ascii=False)[:80])
    chk('客户名已带入 iframe', 'customer=' in r2['src'] and '%E6%9F%90' in r2['src'], r2['src'][:80])
    # 子页确实读到客户
    fr = None
    for f in pg.frames:
        if '20261007p2' in (f.url or ''):
            fr = f
            break
    if fr:
        pg.wait_for_timeout(1200)
        cust = fr.evaluate("() => (document.getElementById('customer')||{}).value || ''")
        chk('子页客户输入框已预填', '某某诊断科技' in cust, cust)

    print('\n④ 订单与业绩 hub')
    pg.evaluate("() => { ordHubView='orders'; state.tab='orders'; render(); }")
    pg.wait_for_timeout(2000)
    o1 = pg.evaluate("() => document.getElementById('ordHubBody').innerHTML.length")
    pg.evaluate("() => { ordHubView='price'; render(); }")
    pg.wait_for_timeout(2000)
    o2 = pg.evaluate("() => document.getElementById('ordHubBody').innerHTML.length")
    chk('两个二级视图都能渲染且不同', o1 > 500 and o2 > 500 and o1 != o2, '%d / %d' % (o1, o2))

    print('\n⑤ 去前准备入口')
    pg.evaluate("() => visitPrepGo('某某生物科技有限公司')")
    pg.wait_for_timeout(2500)
    chk('跳转到访前准备', pg.evaluate("() => state.tab") == 'visitPrep')

    print('\n⑥ 回归：全部导航 Tab')
    tabs = pg.evaluate("() => (window.TABS||[]).map(t=>t.id)")
    lens = []
    for t in tabs:
        n0 = len(errs)
        g = pg.evaluate("""(t) => { window.__e=''; try { switchTab(t); } catch(e) { window.__e=String(e); }
          const m=document.getElementById('main'); return {tab:state.tab, len:m.innerHTML.length, err:window.__e}; }""", t)
        pg.wait_for_timeout(420)
        lens.append(g['len'])
        if g['err'] or g['tab'] != t or len(errs) - n0:
            ok = False
            print('   ❌ %-14s err=%s newerr=%d' % (t, g['err'][:40], len(errs) - n0))
    chk('%d 个 Tab 均可切换' % len(tabs), True)
    chk('内容各不相同', len(set(lens)) >= len(tabs) - 2, '%d 种' % len(set(lens)))
    chk('全程无 JS 错误', not errs, str(errs[:2]))
    pg.screenshot(path='/tmp/p2_nav.png')
    pg.evaluate("() => { visitHubState='see'; state.tab='visitStats'; render(); }")
    pg.wait_for_timeout(2500)
    pg.screenshot(path='/tmp/p2_visit.png')
    br.close()

print('\n%s' % ('✅ P2 全量验收通过' if ok else '❌ 有失败项'))
sys.exit(0 if ok else 1)
