#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""按用户 2026-10-07 拍板，改这 5 条待办的类型（本地 + CloudBase 双写）。

按**正文前缀**定位（不靠序号），同时改 users 与 closed 里同 id 的副本，保证不分裂。
"""
import io
import json
import os
import shutil
import subprocess
import sys
import time

ROOT = '/Users/liuxinyuan/Desktop/Hermes输出-工作类'
LOCAL = os.path.join(ROOT, '数据', 'todos.json')
CLI = '/Users/liuxinyuan/.npm-global/lib/node_modules/@cloudbase/cli/dist/standalone/cli.js'
ENV = 'bier-sales-d0gatbvlx288724e9'
COLL = 'todos'

CHANGES = [
    ('以下是天隆质量客诉的内部排查任务清单', 'internal_complaint', '对内·客诉跟进'),
    ('【研发+生产】评估搭建 mNGS 做 NA 控制方案', 'internal_project', '对内·立项跟进'),
    ('【赵云浩+生产】圣湘 FM2181-4-NA-020303', 'internal_other', '对内·其他（客户需求）'),
    ('【刘子研】天隆 刘小辉 PM202', 'external_test_result', '对外·测试结果'),
    ('【赵云浩】万孚 FE20-50', 'external_test_req', '对外·测试需求'),
]
SCOPE = {'internal_project': 'internal', 'internal_complaint': 'internal',
         'internal_test_plan': 'internal', 'internal_other': 'internal',
         'external_visit': 'external', 'external_test_req': 'external',
         'external_test_result': 'external', 'external_project': 'external',
         'external_appointment': 'external'}


def norm(s):
    return ' '.join((s or '').split())


def cli(cmds):
    p = subprocess.run(['node', CLI, 'db', 'nosql', 'execute', '--command',
                        json.dumps(cmds, ensure_ascii=False), '--json', '-e', ENV],
                       capture_output=True, text=True, timeout=180)
    raw = '\n'.join(l for l in (p.stdout or '').splitlines() if not l.lstrip().startswith('- ')).strip()
    i = raw.find('{')
    return json.loads(raw[i:]) if i >= 0 else {}


def main():
    apply = '--apply' in sys.argv
    store = json.load(io.open(LOCAL, encoding='utf-8'))
    users = store.get('users') or {}
    closed = store.get('closed') or []

    n_before = sum(len(v) for v in users.values() if isinstance(v, list))
    print('待办 %d 条 / 归档 %d 条 —— %s\n' % (n_before, len(closed), 'APPLY' if apply else 'DRY-RUN'))

    hit_ids, report = {}, []
    for pre, to, label in CHANGES:
        found = []
        for u, lst in users.items():
            for t in (lst or []):
                if norm(t.get('text')).startswith(pre):
                    found.append((u, t, 'users'))
        for t in closed:
            if norm(t.get('text')).startswith(pre):
                found.append((t.get('salesperson') or '—', t, 'closed'))
        if not found:
            print('  ❌ 未命中：%s' % pre[:34])
            return 1
        for u, t, where in found:
            old = t.get('type') or t.get('cat')
            report.append((u, where, old, to, label, norm(t.get('text'))[:46], t.get('id')))
            if to != old:
                hit_ids[t.get('id')] = to
            if apply:
                t['type'] = to
                t['cat'] = to
                t['scope'] = SCOPE[to]
                t['updated'] = time.strftime('%Y-%m-%d %H:%M:%S')

    print('  命中明细：')
    for u, where, old, to, label, txt, tid in report:
        print('   [%-4s/%-6s] %-22s → %-22s  %s' % (u, where, old, to, txt))
    print('\n  涉及不同 id %d 个' % len(hit_ids))

    if not apply:
        print('\n干跑结束，未写入。')
        return 0

    ts = time.strftime('%Y%m%d-%H%M%S')
    bak = os.path.join(ROOT, '_bak_p0_retype_%s' % ts)
    os.makedirs(bak, exist_ok=True)
    shutil.copy2(LOCAL, os.path.join(bak, 'todos.json'))
    print('  已备份 → %s' % bak)

    store['updated_at'] = time.strftime('%Y-%m-%d %H:%M:%S')
    io.open(LOCAL, 'w', encoding='utf-8').write(json.dumps(store, ensure_ascii=False, indent=1))
    print('  本地已写')

    # 云端：整体覆盖写受影响用户
    owners = {u for u, where, *_ in report if where == 'users'}
    for u in owners:
        cmd = json.dumps({'update': COLL,
                          'updates': [{'q': {'user': u},
                                       'u': {'$set': {'todos': users.get(u) or [],
                                                      'updated': time.strftime('%Y-%m-%dT%H:%M:%S.000Z')}}}]},
                         ensure_ascii=False)
        cli([{'TableName': COLL, 'CommandType': 'UPDATE', 'Command': cmd}])
        print('  云端已写 %s（%d 条）' % (u, len(users.get(u) or [])))

    time.sleep(2)
    print('\n=== 回读校验 ===')
    l2 = json.load(io.open(LOCAL, encoding='utf-8'))
    n_after = sum(len(v) for v in l2['users'].values() if isinstance(v, list))
    print('  %s 本地条数 %d → %d' % ('✅' if n_after == n_before else '❌', n_before, n_after))
    still = []
    for pre, to, label in CHANGES:
        got = [t.get('type') for lst in l2['users'].values() if isinstance(lst, list)
               for t in lst if norm(t.get('text')).startswith(pre)]
        if to not in got:
            still.append(pre[:26])
    print('  %s 5 处类型已生效' % ('✅' if not still else '❌ 未生效: ' + str(still)))

    r = cli([{'TableName': COLL, 'CommandType': 'QUERY',
              'Command': json.dumps({'find': COLL, 'filter': {}, 'limit': 100})}])
    docs = r.get('data', {}).get('results', [[]])[0]
    ncb = sum(len(d.get('todos') or []) for d in docs)
    print('  %s 云端条数 %d' % ('✅' if ncb == n_before else '❌', ncb))
    bad = [d.get('user') for d in docs
           for pre, to, _ in CHANGES
           if norm(next((t.get('text') for t in (d.get('todos') or [])
                         if norm(t.get('text')).startswith(pre)), '')).startswith(pre)
           and to not in [t.get('type') for t in (d.get('todos') or [])
                          if norm(t.get('text')).startswith(pre)]]
    print('  %s 云端 5 处类型已生效' % ('✅' if not bad else '❌ ' + str(set(bad))))
    return 0


sys.exit(main())
