#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""P0 回归 v3（最终）：用真实会话令牌，按 innerHTML 长度判各 Tab 是否真的各自渲染。

前两版失败的根因（均为探针问题，非产品问题）：
  v1：没调 loadData() → state.loading=true → 每个 Tab 都渲染同一张骨架图；
  v2：用了假 token → 401 清除会话 → state.user 变 null → render() 全部早退（内容冻结）；
      且用 innerText 度量在 2.7MB DOM 上不可靠，v3 改用 innerHTML 长度。
"""
import json
import os
import sys
from playwright.sync_api import sync_playwright

W = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
BASE = 'http://127.0.0.1:8765/'
USER = '刘新元'
KEY = 'bior_sw_todos_v2_' + USER
seed = json.load(open(W + '/数据/todos.json', encoding='utf-8'))['users'][USER]

# 取该用户自己的真实会话令牌（回退：expire_at 最大的那个）
sess = json.load(open(W + '/数据/auth_sessions.json', encoding='utf-8'))
mine = [(v['expire_at'], k, v) for k, v in sess.items() if v.get('name') == USER]
if not mine:
    mine = [(v['expire_at'], k, v) for k, v in sess.items() if v.get('role') in ('admin', 'gm')]
mine.sort(reverse=True, key=lambda x: x[0])
_, TOKEN, IDENT = mine[0]
print('用会话：%s / role=%s / 过期 %.0f' % (IDENT.get('name'), IDENT.get('role'), IDENT['expire_at']))
ident = dict(IDENT); ident['open_id'] = 'ou_preview_only'
del ident['expire_at']

ok = True
def chk(label, cond, detail=''):
    global ok
    print('  %s %-34s %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, k, s]) => {
        localStorage.setItem('bior_auth_token', tk);
        localStorage.setItem('bior_sw_user_v2', JSON.stringify(idn));
        localStorage.setItem(k, JSON.stringify(s));
    }""", [TOKEN, ident, KEY, seed])
    pg.goto(BASE + '工具/index.html', wait_until='load')
    pg.wait_for_timeout(2500)
    pg.evaluate("""(idn) => {
        window.state = window.state || {};
        window.state.user = JSON.parse(JSON.stringify(idn));
        window.__push = 0; pushTodos = function(){ window.__push++; };
        if (typeof showApp === 'function') showApp();
    }""", ident)
    pg.evaluate("() => { if (typeof loadData === 'function') loadData(); }")
    for _ in range(25):
        pg.wait_for_timeout(1000)
        st = pg.evaluate("() => ({l: state.loading, n: Object.keys(state.data||{}).length, u: !!(state.user)})")
        if st['n'] >= 8 and not st['l']:
            break
    print('state.data 键数=%d loading=%s user 在位=%s\n' % (st['n'], st['l'], st['u']))

    tabs = pg.evaluate("() => (window.TABS || []).map(t => t.id)")
    print('共 %d 个 Tab' % len(tabs))
    res = []
    for t in tabs:
        n0 = len(errs)
        r = 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, u: !!(state.user), err: window.__e}; }""", t)
        pg.wait_for_timeout(600)
        res.append((t, r, len(errs) - n0))

    print('\n逐 Tab（innerHTML 长度）：')
    lens = []
    for t, r, de in res:
        good = r['err'] == '' and de == 0 and r['tab'] == t and r['len'] > 200 and r['u']
        if not good:
            ok = False
        lens.append(r['len'])
        print('  %s %-15s 长度 %8d | user在位 %s | 新错误 %d %s'
              % ('✅' if good else '❌', t, r['len'], r['u'], de, ('| ' + r['err'][:50]) if r['err'] else ''))
    chk('各 Tab 内容各不相同', len(set(lens)) >= 14, '%d 个不同长度 / %d 个 Tab' % (len(set(lens)), len(lens)))
    chk('会话全程未被清', all(r['u'] for _, r, _ in res))

    pg.evaluate("() => switchTab('todos')")
    pg.wait_for_timeout(1500)
    td = pg.evaluate("""() => {
      const T = e => (e ? e.innerText.replace(/\\s+/g,' ').trim() : '');
      return {hasForm: !!document.getElementById('todoCat'),
              opts: (() => { const s=document.getElementById('todoCat'); return s?s.querySelectorAll('option').length:0; })(),
              heads: [...new Set([...document.querySelectorAll('#main div[style*="font-weight:800"]')].map(T))],
              rows: document.querySelectorAll('.todo-item').length};
    }""")
    chk('待办 Tab 新 UI 复检', td['hasForm'] and td['opts'] == 9 and len(td['heads']) == 2 and td['rows'] > 0,
        json.dumps(td, ensure_ascii=False))
    chk('全程无 JS 错误', not errs, str(errs[:2]))

    pg.evaluate("([k]) => { localStorage.removeItem(k); localStorage.removeItem('bior_auth_token'); localStorage.removeItem('bior_sw_user_v2'); }", [KEY])
    br.close()

print('\n%s' % ('✅ P0 回归通过：其余 Tab 未受影响' if ok else '❌ 回归发现问题'))
sys.exit(0 if ok else 1)
