"""
批量修正单元挑战听力能力标签 v2 (使用 list_records + update)
"""
import json, urllib.request, re, sys

APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf"
CRED_FILE = "/root/.openclaw/credentials/xiaoyan/config.json"

STD_HEARING_TAGS = {
    "显性事实理解｜关键词识别", "显性事实理解｜单句信息点抓取",
    "基础语境理解｜场景/物品/动作识别", "显性细节理解｜数字/时间/地点",
    "同义替换识别｜词/短语级", "目的/偏好识别｜显性 to/for/like",
    "干扰抑制｜多信息筛选", "语用推断｜否定与纠错",
    "多句保持｜信息整合", "情绪/态度理解", "长对话理解｜主旨+细节",
}

TAG_PATTERNS = [
    (r'显性事实理解（关键词识别）', '显性事实理解｜关键词识别'),
    (r'显性事实理解（单句信息点抓取）', '显性事实理解｜单句信息点抓取'),
    (r'显性事实理解（单句信息点抓取：.*?）', '显性事实理解｜单句信息点抓取'),
    (r'显性细节理解（地点）', '显性细节理解｜数字/时间/地点'),
    (r'显性细节理解（地点：.*?）', '显性细节理解｜数字/时间/地点'),
    (r'显性细节理解（数字）', '显性细节理解｜数字/时间/地点'),
    (r'显性细节理解（数字：.*?）', '显性细节理解｜数字/时间/地点'),
    (r'显性细节理解（时间）', '显性细节理解｜数字/时间/地点'),
    (r'显性细节理解（复现）', '显性细节理解｜数字/时间/地点'),
    (r'干扰抑制（多信息筛选）', '干扰抑制｜多信息筛选'),
    (r'干扰抑制（多信息筛选：.*?）', '干扰抑制｜多信息筛选'),
    (r'偏好识别（显性\s*like\s*）', '目的/偏好识别｜显性 to/for/like'),
    (r'偏好识别（显性\s*love\s*）', '目的/偏好识别｜显性 to/for/like'),
    (r'目的识别（显性\s*for\s*）', '目的/偏好识别｜显性 to/for/like'),
    (r'目的识别（显性\s*to\s*）', '目的/偏好识别｜显性 to/for/like'),
    (r'问题意图识别', '语用推断｜否定与纠错'),
    (r'关键细节听辨', '显性细节理解｜数字/时间/地点'),
    (r'否定与纠错', '语用推断｜否定与纠错'),
    (r'图像语义对齐', '基础语境理解｜场景/物品/动作识别'),
    (r'近义改写', '同义替换识别｜词/短语级'),
    (r'多句保持（信息整合）', '多句保持｜信息整合'),
    (r'听觉抓取关键信息', '显性事实理解｜关键词识别'),
    (r'多特征整合', '多句保持｜信息整合'),
    (r'语用推断（句式理解）', '语用推断｜否定与纠错'),
    (r'语用推断（否定与纠错）', '语用推断｜否定与纠错'),
    (r'语用推断（语气识别）', '语用推断｜否定与纠错'),
    (r'语境理解（场景推断）', '基础语境理解｜场景/物品/动作识别'),
    (r'情绪态度理解', '情绪/态度理解'),
    (r'长对话主旨\+细节', '长对话理解｜主旨+细节'),
    (r'语用推断', '语用推断｜否定与纠错'),
    (r'显性细节理解', '显性细节理解｜数字/时间/地点'),
    (r'显性事实理解', '显性事实理解｜关键词识别'),
    (r'干扰抑制', '干扰抑制｜多信息筛选'),
]

def map_tag(tag):
    tag = tag.strip()
    if not tag:
        return None
    if tag in STD_HEARING_TAGS:
        return tag
    for pattern, replacement in TAG_PATTERNS:
        if re.match(pattern, tag):
            return replacement
    # Last resort: try partial match
    for pattern, replacement in TAG_PATTERNS:
        if re.search(pattern, tag):
            return replacement
    return None

def map_ability_list(abilities):
    if not abilities:
        return []
    result = []
    for a in abilities:
        if not isinstance(a, str):
            continue
        # Handle ¥¥ delimiter
        for part in a.split('¥¥'):
            part = part.strip()
            if not part:
                continue
            # Remove trailing " - X" annotations like " - red", " - blue"
            part = re.sub(r'\s*-\s*\w+\s*$', '', part)
            part = part.strip()
            mapped = map_tag(part)
            if mapped and mapped not in result:
                result.append(mapped)
    return result

def get_token():
    with open(CRED_FILE) as f:
        cfg = json.load(f)
    app_id = cfg['apps'][0]['appId']
    app_secret = cfg['apps'][0]['appSecret']
    req = urllib.request.Request(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        data=json.dumps({"app_id": app_id, "app_secret": app_secret}).encode(),
        headers={"Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req).read())['tenant_access_token']

def api_call(url, method='GET', body=None):
    token = get_token()
    headers = {"Authorization": f"Bearer {token}"}
    data = json.dumps(body).encode() if body else None
    if data:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, method=method, headers=headers)
    return json.loads(urllib.request.urlopen(req).read())

def fix_tags_in_json(jd):
    changed = False
    for section in ['first', 'second']:
        sect = jd.get(section, {})
        if not sect:
            continue
        qs = sect.get('questionSet', [])
        for q in qs:
            old = q.get('ability', [])
            new = map_ability_list(old)
            if new != old:
                q['ability'] = new
                changed = True
    # Root questionSet (P6)
    qs = jd.get('questionSet', [])
    for q in qs:
        old = q.get('ability', [])
        new = map_ability_list(old)
        if new != old:
            q['ability'] = new
            changed = True
    # Root ability (P5/P7)
    if 'ability' in jd:
        old = jd.get('ability', [])
        new = map_ability_list(old)
        if new != old:
            jd['ability'] = new
            changed = True
    # answerSet ability (P5)
    # (already handled by root ability)
    return jd, changed

# ===== Main =====
targets = {
    "听力-P1": "tbliZAhcc9C43B23",
    "听力-P2": "tblzTLNH7f13uWQN",
    "听力-P4": "tblVmeDtBDKsAEfz",
    "听力-P7": "tbly9SvPEa44k3yX",
    "听力-P5": "tblDssVmhGzc3UKd",
    "听力-P6": "tbloiMcD0sBtGSTq",
}

all_summary = []

for name, table_id in targets.items():
    print(f"\n{'='*50}")
    print(f"{name}")
    
    # List all records
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=500"
    resp = api_call(url)
    if resp.get('code') != 0:
        print(f"  ❌ API error: {resp.get('msg')}")
        continue
    
    items = resp['data']['items']
    total = len(items)
    skip010199 = 0
    skip_empty = 0
    no_change = 0
    updated = 0
    failed = 0
    
    for item in items:
        fields = item.get('fields', {})
        sid = fields.get('题目集合 ID', '') or ''
        rid = item['record_id']
        
        if '010199' in str(sid):
            skip010199 += 1
            continue
        
        jd_str = fields.get('jsonData', '{}')
        if not jd_str or jd_str == 'None':
            skip_empty += 1
            continue
        
        try:
            jd = json.loads(jd_str)
        except json.JSONDecodeError as e:
            print(f"  ⚠️ ID={sid}: JSON parse error, skipping")
            failed += 1
            continue
        
        jd_fixed, changed = fix_tags_in_json(jd)
        
        if not changed:
            no_change += 1
            continue
        
        # Update record
        new_jd_str = json.dumps(jd_fixed, ensure_ascii=False)
        update_url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{rid}"
        upd_resp = api_call(update_url, 'PUT', {"fields": {"jsonData": new_jd_str}})
        
        if upd_resp.get('code') == 0:
            updated += 1
            old_tags = []
            for s in ['first','second']:
                for q in jd.get(s,{}).get('questionSet',[]):
                    old_tags.extend(q.get('ability',[]))
            new_tags = []
            for s in ['first','second']:
                for q in jd_fixed.get(s,{}).get('questionSet',[]):
                    new_tags.extend(q.get('ability',[]))
            if old_tags != new_tags:
                print(f"  ✅ ID={sid}: tags updated [{','.join(new_tags[:3])}...]")
            else:
                print(f"  ✅ ID={sid}: tags updated")
        else:
            failed += 1
            print(f"  ❌ ID={sid}: update failed - {upd_resp.get('msg','?')}")
    
    all_summary.append({
        'name': name, 'total': total, 'skip010199': skip010199,
        'skip_empty': skip_empty, 'no_change': no_change,
        'updated': updated, 'failed': failed
    })
    print(f"  Total={total} Skip010199={skip010199} SkipEmpty={skip_empty} NoChange={no_change} Updated={updated} Failed={failed}")

print(f"\n\n===== 能力标签修正汇总 =====")
for r in all_summary:
    print(f"{r['name']:10s}: total={r['total']:2d} skip010199={r['skip010199']} skip_empty={r['skip_empty']} unchanged={r['no_change']} ✅updated={r['updated']} ❌failed={r['failed']}")
