#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""真实数据验收（活动类型版）：7/8/9 月，KPI 按飞书「活动类型」四类 + 三级下钻 + 详情。"""
import json
from playwright.sync_api import sync_playwright

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
BASE = 'http://127.0.0.1:8765/'
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') == '刘新元'],
              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)

ok = True
def chk(label, cond, detail=''):
    global ok
    print('  %s %-44s %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': 1560, 'height': 1050})
    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.wait_for_timeout(1200)
    pg.evaluate("() => { visitView='dept'; visitDrill={dept:'',sales:''}; visitHubState='see'; state.tab='visitStats'; render(); }")
    pg.wait_for_timeout(1200)
    pg.evaluate("() => visitSetMonth('2026-09')")
    pg.wait_for_timeout(5000)

    def snap():
        return pg.evaluate("""() => { const b=document.getElementById('visitBody');
          const T=e=>e?e.innerText.replace(/\\s+/g,' ').trim():'';
          return {txt:T(b), rows:[...b.querySelectorAll('tbody tr')].map(tr=>T(tr)),
                  crumb:T(document.getElementById('visitCrumb')),
                  all:(document.querySelector('main')||document.body).innerText.replace(/\\s+/g,' ')}; }""")

    print('① KPI 按「活动类型」（9 月）')
    s = snap()
    for kw, name in [('活动总数', '活动总数卡'), ('线下拜访', '线下拜访卡'), ('线上跟进', '线上跟进卡'),
                     ('陌拜', '陌拜卡'), ('1380', '线下值 1380'), ('622', '线上值 622'), ('91', '陌拜值 91')]:
        chk('出现「%s」' % name, kw in s['all'], '')
    chk('说明写明取自飞书字段非推断', '不是' in s['all'] and '活动类型' in s['all'])
    chk('已无「形式未标」旧口径', '形式未标' not in s['all'] and '可判定' not in s['all'])
    pg.screenshot(path='/tmp/p2d_1_kpi.png')

    print('\n② 表头列 = 活动类型')
    heads = pg.evaluate("() => [...document.querySelectorAll('#visitBody thead th')].map(e=>e.innerText.trim())")
    chk('表头含三种活动类型', all(h in heads for h in ('线下拜访', '线上跟进', '陌拜')), str(heads))

    print('\n③ 部门表 + 守恒（9 月）')
    j = pg.evaluate("() => ({t:window.__vj.total, ty:window.__vj.types, deps:(window.__vj.departments||[]).map(d=>({d:d.dept,t:d.total,ty:d.types,s:d.sales_count}))})")
    so = sum(d['ty'].get('线下拜访', 0) for d in j['deps'])
    sn = sum(d['ty'].get('线上跟进', 0) for d in j['deps'])
    chk('总数 2093', j['t'] == 2093, str(j['t']))
    chk('守恒：线下拜访之和 == KPI', so == j['ty'].get('线下拜访'), '%d vs %d' % (so, j['ty'].get('线下拜访')))
    chk('守恒：线上跟进之和 == KPI', sn == j['ty'].get('线上跟进'), '%d vs %d' % (sn, j['ty'].get('线上跟进')))
    chk('部门 4 个', len(j['deps']) == 4, '、'.join(d['d'] for d in j['deps']))

    print('\n④ 日期切换：7 / 8 月')
    for m, exp in [('2026-08', 2016), ('2026-07', 1867)]:
        pg.evaluate("(mm) => visitSetMonth(mm)", m)
        pg.wait_for_timeout(4000)
        t = pg.evaluate("() => (window.__vj||{}).total")
        nd = pg.evaluate("() => ((window.__vj||{}).departments||[]).length")
        chk('%s 总数 %d ｜ 部门 %d' % (m, exp, nd), t == exp, str(t))
    pg.evaluate("() => visitSetMonth('2026-09')")
    pg.wait_for_timeout(4000)

    print('\n⑤ 下钻 部门 → 销售 → 客户 → 明细 → 详情')
    top = pg.evaluate("""() => { const d=(window.__vj.departments||[]).find(x=>x.dept==='生命科学销售部');
        return d&&d.sales.length?d.sales[0].name:null; }""")
    pg.evaluate("() => visitDrillTo('生命科学销售部','')")
    pg.wait_for_timeout(1000)
    s = snap()
    chk('面包屑到部门', '生命科学销售部' in s['crumb'], s['crumb'][:60])
    chk('列出该部门销售（动态首位=%s）' % top, top in s['txt'])
    pg.evaluate("(n) => visitDrillTo('生命科学销售部', n)", top)
    pg.wait_for_timeout(1300)
    s = snap()
    chk('面包屑到销售', top in s['crumb'], s['crumb'][:70])
    chk('列出客户行', len(s['rows']) >= 1, '%d 行' % len(s['rows']))
    pg.screenshot(path='/tmp/p2d_2_cust.png')
    pg.evaluate("() => { const b=document.querySelector('#visitBody tbody tr button'); if(b) b.click(); }")
    pg.wait_for_timeout(900)
    n_acts = pg.evaluate("() => document.querySelectorAll('#visitBody tbody td div[id^=visitDetail]').length")
    chk('展开出活动明细', n_acts >= 1, '%d 条' % n_acts)
    acts = pg.evaluate("() => { const t=document.querySelector('#visitBody tbody td[colspan]'); return t?t.innerText.replace(/\\s+/g,' ').trim():''; }")
    chk('明细带活动类型标签', any(k in acts for k in ('线下拜访', '线上跟进', '陌拜')), acts[:64])
    pg.evaluate("() => { const a=[...document.querySelectorAll('#visitBody a')].find(x=>x.innerText.indexOf('看详情')>=0); if(a) a.click(); }")
    pg.wait_for_timeout(2200)
    det = pg.evaluate("() => { const d=document.querySelector('#visitBody td div[id^=visitDetail]'); return d?d.innerText.replace(/\\s+/g,' ').trim():''; }")
    chk('详情含【活动类型】', '【活动类型】' in det, det[:70])
    chk('详情含【飞书跟进部门】', '【飞书跟进部门】' in det)
    pg.screenshot(path='/tmp/p2d_3_detail.png')

    print('\n⑥ 我的 / 按客户')
    pg.evaluate("() => visitSetView('me')")
    pg.wait_for_timeout(4000)
    mine_n = pg.evaluate("() => ((window.__vj||{}).customers||[]).length")
    chk('「我的」客户数 0<n<369', 0 < mine_n < 369, '我的 %d 家' % mine_n)
    pg.evaluate("() => visitSetView('cust')")
    pg.wait_for_timeout(4000)
    s = snap()
    chk('「按客户」出现客户数', '家客户' in s['all'])
    chk('筛选框可用', pg.evaluate("() => !!document.querySelector('#visitBody input[placeholder]')"))

    print('\n⑦ 无 JS 错误')
    chk('0 JS 错误', len(errs) == 0, str(errs[:2]))
    br.close()

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