"""
批量修正单元挑战听力能力标签 + JSON修复
"""
import json, urllib.request, re, sys, os

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

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

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

def map_tag(tag):
    """Map a single old tag to standard format. Returns None if no match."""
    tag = tag.strip()
    if tag in STD_HEARING_TAGS:
        return tag
    for pattern, replacement in TAG_PATTERNS:
        if re.match(pattern + r'.*$', tag):
            return replacement
    return None

def map_ability_list(abilities):
    """Map a list of ability tags. Handles '¥¥' delimiters."""
    if not abilities:
        return []
    result = []
    for a in abilities:
        if not isinstance(a, str):
            result.append(a)
            continue
        # Handle ¥¥ delimiter
        parts = a.split('¥¥')
        for part in parts:
            part = part.strip()
            if not part:
                continue
            mapped = map_tag(part)
            if mapped and mapped not in result:
                result.append(mapped)
            elif mapped is None:
                # Try to match anyway
                mapped2 = map_tag(part)
                if mapped2 and mapped2 not in result:
                    result.append(mapped2)
    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_get(url):
    token = get_token()
    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
    return json.loads(urllib.request.urlopen(req).read())

def api_put(url, body):
    token = get_token()
    req = urllib.request.Request(url, data=json.dumps(body).encode(), method="PUT",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req).read())

def update_record(table_id, record_id, fields):
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}"
    return api_put(url, {"fields": fields})

def fix_tags_in_json(jd):
    """Fix ability tags in jsonData - handles first/second questionSet and root questionSet"""
    changed = False
    
    # Handle first/second structure
    for section in ['first', 'second']:
        sect = jd.get(section, {})
        if not sect:
            continue
        qs = sect.get('questionSet', [])
        for q in qs:
            old_ability = q.get('ability', [])
            new_ability = map_ability_list(old_ability)
            if new_ability != old_ability:
                q['ability'] = new_ability
                changed = True
    
    # Handle root questionSet (P6 style)
    qs = jd.get('questionSet', [])
    for q in qs:
        old_ability = q.get('ability', [])
        new_ability = map_ability_list(old_ability)
        if new_ability != old_ability:
            q['ability'] = new_ability
            changed = True
    
    # Handle P5/P7 style: ability at root level
    if 'ability' in jd:
        old_ability = jd.get('ability', [])
        new_ability = map_ability_list(old_ability)
        if new_ability != old_ability:
            jd['ability'] = new_ability
            changed = True
    
    return jd, changed

def fix_tags_for_record(record_id, table_id, table_name):
    """Fix ability tags for a single record"""
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}"
    data = api_get(url)
    record = data['data']['items'][0]
    fields = record['fields']
    
    jd_str = fields.get('jsonData', '{}')
    if not jd_str or jd_str == 'None':
        return False, "empty jsonData"
    
    try:
        jd = json.loads(jd_str)
    except json.JSONDecodeError as e:
        return False, f"JSON parse error: {e}"
    
    jd_fixed, changed = fix_tags_in_json(jd)
    
    if not changed:
        return True, "no changes needed"
    
    # Update the record with fixed jsonData
    new_jd_str = json.dumps(jd_fixed, ensure_ascii=False)
    result = update_record(table_id, record_id, {"jsonData": new_jd_str})
    
    if result.get('code') == 0:
        return True, "tags updated"
    else:
        return False, f"API error: {result}"

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

all_results = []

for name, table_id in targets.items():
    # Get all records
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=500"
    data = api_get(url)
    
    if data.get('code') != 0:
        print(f"❌ {name}: API error - {data.get('msg')}")
        continue
    
    items = data['data']['items']
    print(f"\n{'='*50}")
    print(f"{name}: {len(items)} records")
    
    total = 0
    skipped = 0
    success = 0
    failed = 0
    
    for item in items:
        fields = item.get('fields', {})
        sid = fields.get('题目集合 ID', '') or ''
        rid = item['record_id']
        
        total += 1
        
        # Skip 010199
        if '010199' in str(sid):
            skipped += 1
            continue
        
        # Skip empty
        jd_str = fields.get('jsonData', '{}')
        if not jd_str or jd_str == 'None':
            skipped += 1
            continue
        
        ok, msg = fix_tags_for_record(rid, table_id, name)
        if ok:
            success += 1
            print(f"  {'✅' if 'updated' in msg else '⏭️'} ID={sid:10s} | {msg}")
        else:
            failed += 1
            print(f"  ❌ ID={sid:10s} | {msg}")
    
    all_results.append({
        'name': name, 'total': total, 'skipped': skipped,
        'success': success, 'failed': failed
    })

print(f"\n\n===== 标签修正汇总 =====")
for r in all_results:
    print(f"{r['name']:10s}: total={r['total']} skipped={r['skipped']} success={r['success']} failed={r['failed']}")
