#!/usr/bin/env python3
"""审计+修复单元挑战多维表格听力题型表的解析(explanation)字段 - v2"""

import requests
import json
import re
import copy

APP_ID = "cli_a931175d41799cc7"
APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14"
APP_TOKEN = "CMHSbUUjka3TrUsaxxEc297ongf"

TABLES = {
    "听力-P1-图片选择题/选图题": "tbliZAhcc9C43B23",
    "听力-P2-表格填空题": "tblzTLNH7f13uWQN",
    "听力-P3-长对话选择": "tblgxsDn25oSq7WS",
    "听力-P4-短对话选择题": "tblVmeDtBDKsAEfz",
    "听力-P5-信息匹配题": "tblDssVmhGzc3UKd",
    "听力-P7-听力拖拽": "tbly9SvPEa44k3yX",
}

# === 检测模式（只判断是否需要修复）===
CHOICE_READING_PATTERNS = [
    (r'干扰项', '含"干扰项"'),
    (r'误选', '含"误选"'),
    (r'所以选[ABCDEFGH]', '含"所以选X"'),
    (r'正确答案是', '含"正确答案是"'),
    (r'如果有人[误错]?选', '含"如果有人错选"'),
    (r'如果[误错]?选', '含"如果误选"'),
    (r'排除[法项]', '含"排除法/项"'),
    (r'干扰[因素析]', '含干扰分析用语'),
    (r'容易混淆|容易选错|可能会选|不要选', '含混淆/选错提示'),
    (r'因此选[ABCDEFGH][，。]', '含"因此选X"'),
    (r'故选[ABCDEFGH][，。]', '含"故选X"'),
    (r'其他选项[^，。,.\n]{0,30}(没有|不是|错误|不对)', '含"其他选项没有/错误"'),
    (r'选项[ABCDEFGH][^，。,.\n]{0,20}(没有|不在|不是)', '含具体选项否定分析'),
    (r'虽然[^，。,.\n]{0,20}(接近|相似|类似)', '含"虽然…接近"'),
    (r'材料中[^，。,.\n]{0,10}(写到|明确说)', '含"材料中写到/明确说"'),
    (r'原文中[^，。,.\n]{0,10}(写到|说)', '含"原文中写到"'),
    (r'如果有人选', '含干扰选分析'),
]

ABILITY_MISMATCH = [
    (r'写作能力|书面表达|写作技巧', '含写作能力标签'),
    (r'口语表达|发音准确|口语技巧', '含口语能力标签'),
    (r'拼写能力|拼写规则', '含拼写能力标签'),
    (r'阅读理解|阅读技巧|快速阅读', '含阅读能力标签'),
]


def get_token():
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": APP_ID, "app_secret": APP_SECRET},
        timeout=30,
    )
    return resp.json()["tenant_access_token"]


def get_all_records(token, table_id):
    records = []
    page_token = None
    while True:
        url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records?page_size=500"
        if page_token:
            url += f"&page_token={page_token}"
        resp = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=30)
        data = resp.json()
        if data.get("code") != 0:
            print(f"  ERROR: {data.get('code')} {data.get('msg')}")
            break
        items = data.get("data", {}).get("items", [])
        records.extend(items)
        if not data.get("data", {}).get("has_more"):
            break
        page_token = data.get("data", {}).get("page_token")
    return records


def parse_json_data(fields):
    jd = fields.get("jsonData")
    if not jd:
        return None
    if isinstance(jd, str):
        try:
            return json.loads(jd)
        except json.JSONDecodeError:
            return None
    return jd


def get_parts(json_data):
    parts = []
    for key in ['first', 'second', 'third', 'fourth']:
        if key in json_data and isinstance(json_data[key], dict):
            parts.append(key)
    return parts


def check_one(exp_text):
    """检查单个explanation是否有问题"""
    if not exp_text or not exp_text.strip():
        return False, ""
    for pattern, desc in CHOICE_READING_PATTERNS:
        if re.search(pattern, exp_text):
            return True, desc
    for pattern, desc in ABILITY_MISMATCH:
        if re.search(pattern, exp_text):
            return True, desc
    return False, ""


def fix_explanation(exp_text):
    """修复单个explanation"""
    if not exp_text or not isinstance(exp_text, str):
        return exp_text
    
    exp = exp_text.strip()
    
    # === 移除选择题/阅读题特有用语 ===
    
    # "干扰项分析" → 移除
    exp = re.sub(r'干扰项分析[：:][^。！？…]{0,80}', '', exp)
    exp = re.sub(r'干扰项[^，。,.\n]{0,40}[，。,.\n]?', '', exp)
    
    # "如果有人错选/如果误选…" → 移除（保留前面的内容）
    exp = re.sub(r'[；;]\s*如果有人[误错]?选[^。！？…]{0,80}', '。', exp)
    exp = re.sub(r'[；;]\s*如果[误错]?选[^。！？…]{0,80}', '。', exp)
    # 单独出现的"如果有人选错"子句
    exp = re.sub(r'，如果有人[误错]?选[^。！？…]{0,80}', '。', exp)
    
    # "如果选错/如果误选…" — 更广匹配
    exp = re.sub(r'[；;，,]\s*如果选错[^。！？…]{0,80}', '。', exp)
    exp = re.sub(r'[；;，,]\s*如果误选[^。！？…]{0,80}', '。', exp)
    
    # "所以选X" → 移除
    exp = re.sub(r'所以选[ABCDEFGH][，。]?', '', exp)
    
    # "因此选X。" → 移除
    exp = re.sub(r'，因此选[ABCDEFGH]。', '。', exp)
    exp = re.sub(r'因此选[ABCDEFGH][，。]?', '', exp)
    
    # "故选X。" → 改为自然表述
    exp = re.sub(r'，故选[ABCDEFGH][，。]?', '。', exp)
    exp = re.sub(r'故选[ABCDEFGH][，。]?', '', exp)
    
    # "正确答案是X[选项]" → 移除，保留前后内容
    exp = re.sub(r'[，,]?\s*因此正确答案是[^，。]{0,30}选项[ABC][，。]?', '。', exp)
    exp = re.sub(r'[，,]?\s*因此正确答案是[ABC][，。]?', '。', exp)
    exp = re.sub(r'[，,]?\s*正确答案是[^，。]{0,30}选项[ABC][，。]?', '。', exp)
    exp = re.sub(r'[，,]?\s*正确答案是[ABC][，。]?', '。', exp)
    exp = re.sub(r'[，,]?\s*正确答案是\s*B\s*\.\s*draw[^。]{0,50}', '', exp)
    exp = re.sub(r'[，,]?\s*正确答案是\s*[ABCL]\s*\.\s*[a-z][^。！？…]{0,30}[，。]?', '', exp)
    
    # "答案是X" → 更自然
    exp = re.sub(r'答案是([ABCDEFGH])[，。]?', r'对应选项\1。', exp)
    exp = re.sub(r'答案是([^，。]{1,20})[，。]?', r'答案为\1。', exp)
    
    # "排除法/排除项"
    exp = re.sub(r'排除[法项][^，。,.\n]{0,30}[，。,.\n]?', '', exp)
    
    # "容易混淆/容易选错"
    exp = re.sub(r'[，。]?\s*容易混淆[^，。,.\n]{0,40}[，。,.\n]?', '', exp)
    exp = re.sub(r'[，。]?\s*容易选错[^，。,.\n]{0,40}[，。,.\n]?', '', exp)
    exp = re.sub(r'[，。]?\s*可能会选错[^，。,.\n]{0,40}[，。,.\n]?', '', exp)
    
    # "其他选项没有/错误…" → 移除
    exp = re.sub(r'[，,]?\s*选择[ABC]是正确的[，,]?\s*因为其他选项[^。！？…]{0,80}[。]?', '', exp)
    exp = re.sub(r'[，,]?\s*其他选项[^。！？…]{0,40}[，。,.\n]?', '', exp)
    
    # "选项X没有/不在/不是…" → 移除
    exp = re.sub(r'选项[ABCDEFGH][^，。,.\n]{0,25}(?:没有|不在|不是|不正确|错误)[^，。,.\n]{0,20}[，。,.\n]?', '', exp)
    
    # "虽然X接近/相似…" → 移除
    exp = re.sub(r'[，。]?\s*虽然[^，。,.\n]{0,20}(?:接近|相似|类似)[^，。,.\n]{0,50}[，。,.\n]?', '', exp)
    
    # "材料中…" → "音频中…"
    exp = re.sub(r'材料中([^，。,.\n]{0,10})(提到|写到|说)', r'音频中\1\2', exp)
    exp = re.sub(r'原文中([^，。,.\n]{0,10})(写到|说|提到)', r'音频中\1\2', exp)
    
    # "干扰因素/干扰析"
    exp = re.sub(r'干扰[因素析][^，。,.\n]{0,30}[，。,.\n]?', '', exp)

    # === 清理 ===
    # 只清理中文标点周围的空格，保留英文单词间的空格
    # 移除中文文本中多余的空格（中文字符之间的空格）
    # 但保留英文单词/数字之间的空格
    def clean_spaces(m):
        return m.group(0).replace(' ', '')
    
    # 中文字符+可选空格+中文字符 → 移除空格
    exp = re.sub(r'[\u4e00-\u9fff] +[\u4e00-\u9fff]', lambda m: m.group(0).replace(' ', ''), exp)
    # 中文标点+空格+中文字符
    exp = re.sub(r'([，。！？…、；：""''（）【】]) +([\u4e00-\u9fff])', r'\1\2', exp)
    # 中文字符+空格+中文标点
    exp = re.sub(r'([\u4e00-\u9fff]) +([，。！？…、；：""''（）【】])', r'\1\2', exp)
    
    # 清理多余标点（中文）
    exp = re.sub(r'[，,]\s*[，,]', '，', exp)
    exp = re.sub(r'[。.]\s*[。.]', '。', exp)
    exp = re.sub(r'[；;]\s*[；;]', '；', exp)
    
    # 移除开头多余的逗号
    exp = re.sub(r'^[，,]\s*', '', exp)
    exp = re.sub(r'^[。.]\s*', '', exp)
    
    # 移除"因此。"这样的孤悬词（没有后续内容）
    exp = re.sub(r'因此[。]{2,}', '。', exp)
    
    # 如果explanation以"因此。"结尾且之前有完整内容，保留之前的句号
    # 一般性地清理
    exp = exp.strip()
    
    # 如果清理后几乎为空
    if not exp or len(exp) < 8:
        return "根据音频中的关键信息进行判断，确认答案。"
    
    # 确保以句号结尾
    if exp and not exp.endswith(('。', '！', '？', '…', '.')):
        exp += '。'
    
    # 把英文句号结尾的也改为中文句号（除非是英文缩写点）
    exp = re.sub(r'([\u4e00-\u9fff])\.$', r'\1。', exp)
    
    return exp


def fix_p5_explanation(exp_text):
    """修复P5信息匹配题的整段explanation（¥¥分隔）"""
    if not exp_text or not isinstance(exp_text, str):
        return exp_text
    
    segments = exp_text.split('¥¥')
    fixed_segments = []
    
    for seg in segments:
        seg = seg.strip()
        if not seg:
            continue
        fixed = fix_explanation(seg)
        if fixed:
            fixed_segments.append(fixed)
    
    return '¥¥'.join(fixed_segments)


def process_record(json_data, table_name=""):
    """检查+修复单条记录，返回fixed_count, problem_count, modified"""
    parts = get_parts(json_data)
    if not parts:
        return 0, 0, False
    
    modified = False
    total_fixed = 0
    total_problems = 0
    
    for part_key in parts:
        part = json_data[part_key]
        part_type = part.get("type", "")
        
        # === 处理 questionSet ===
        if "questionSet" in part:
            question_set = part["questionSet"]
            if not isinstance(question_set, list):
                continue
            
            # 收集所有非空explanation
            all_exps = []
            for qi, q in enumerate(question_set):
                if not isinstance(q, dict):
                    continue
                exp = q.get("explanation", "")
                if exp and isinstance(exp, str) and exp.strip():
                    all_exps.append((qi, exp.strip()))
            
            non_empty = [e[1] for e in all_exps]
            all_same = len(non_empty) > 1 and len(set(non_empty)) == 1
            
            for qi, q in enumerate(question_set):
                if not isinstance(q, dict):
                    continue
                exp = q.get("explanation", "")
                if not exp or not isinstance(exp, str) or not exp.strip():
                    continue
                
                has_problem, reason = check_one(exp)
                
                if all_same and len(non_empty) > 1:
                    has_problem = True
                    reason = "同一部分内所有子题explanation完全相同"
                
                if has_problem:
                    total_problems += 1
                    old_exp = exp
                    fixed = fix_explanation(exp)
                    q["explanation"] = fixed
                    modified = True
                    total_fixed += 1
                    print(f"  [{table_name}] {part_key}.Q{qi+1}: {reason}")
                    print(f"    OLD: {old_exp[:120]}...")
                    print(f"    NEW: {fixed[:120]}...")
        
        # === 处理 top-level explanation (P5, P7) ===
        if "explanation" in part and part_type in ("listening_matchInfo", "listening_drag"):
            exp = part.get("explanation", "")
            if not exp or not isinstance(exp, str) or not exp.strip():
                continue
            
            has_problem, reason = False, ""
            
            if part_type == "listening_matchInfo":
                # 检查每个¥¥分隔的段落
                segments = exp.split('¥¥')
                for seg in segments:
                    seg = seg.strip()
                    if not seg:
                        continue
                    sp, sr = check_one(seg)
                    if sp:
                        has_problem = True
                        reason = sr
                        break
            else:
                has_problem, reason = check_one(exp)
            
            if has_problem:
                total_problems += 1
                old_exp = exp
                if part_type == "listening_matchInfo":
                    fixed = fix_p5_explanation(exp)
                else:
                    fixed = fix_explanation(exp)
                part["explanation"] = fixed
                modified = True
                total_fixed += 1
                print(f"  [{table_name}] {part_key}.explanation: {reason}")
                print(f"    OLD: {old_exp[:150]}...")
                print(f"    NEW: {fixed[:150]}...")
    
    return total_fixed, total_problems, modified


def update_record(token, table_id, record_id, json_data):
    json_str = json.dumps(json_data, ensure_ascii=False)
    update_body = {
        "fields": {
            "jsonData": json_str,
            "审校结果": "修复解析"
        }
    }
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{table_id}/records/{record_id}"
    resp = requests.put(
        url,
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json; charset=utf-8"
        },
        json=update_body,
        timeout=30,
    )
    result = resp.json()
    if result.get("code") != 0:
        print(f"    UPDATE FAILED: {result.get('code')} {result.get('msg')}")
        return False
    return True


def process_table(token, table_name, table_id):
    print(f"\n{'='*60}")
    print(f"表: {table_name} ({table_id})")
    print(f"{'='*60}")
    
    records = get_all_records(token, table_id)
    print(f"总记录数: {len(records)}")
    
    total_fixed = 0
    total_problems = 0
    total_updated = 0
    
    for record in records:
        record_id = record.get("record_id")
        fields = record.get("fields", {})
        
        json_data = parse_json_data(fields)
        if not json_data:
            continue
        
        updated_data = copy.deepcopy(json_data)
        fixed, problems, modified = process_record(updated_data, table_name)
        
        total_fixed += fixed
        total_problems += problems
        
        if modified:
            success = update_record(token, table_id, record_id, updated_data)
            if success:
                total_updated += 1
    
    print(f"\n表统计: 有问题{total_problems}处, 修复{total_fixed}处, 更新{total_updated}条记录")
    return {
        "table_name": table_name,
        "total_records": len(records),
        "total_problems": total_problems,
        "total_fixed": total_fixed,
        "total_updated": total_updated,
    }


def main():
    print("=" * 60)
    print("听力题型解析(explanation)字段审计与修复 v2")
    print("=" * 60)
    
    token = get_token()
    print(f"Token: {token[:15]}...")
    
    all_results = []
    for table_name, table_id in TABLES.items():
        result = process_table(token, table_name, table_id)
        all_results.append(result)
    
    # 汇总
    print("\n\n" + "=" * 70)
    print("审计汇总 v2")
    print("=" * 70)
    
    total_problems = sum(r["total_problems"] for r in all_results)
    total_fixed = sum(r["total_fixed"] for r in all_results)
    total_updated = sum(r["total_updated"] for r in all_results)
    
    print(f"\n{'表名':<30} {'记录':>5} {'有问题':>6} {'修复':>6} {'更新记录':>8}")
    print("-" * 65)
    for r in all_results:
        print(f"{r['table_name']:<30} {r['total_records']:>5} {r['total_problems']:>6} {r['total_fixed']:>6} {r['total_updated']:>8}")
    print("-" * 65)
    print(f"{'合计':<30} {'':>5} {total_problems:>6} {total_fixed:>6} {total_updated:>8}")
    
    output = {
        "summary": {"total_problems": total_problems, "total_fixed": total_fixed, "total_updated": total_updated},
        "tables": all_results,
    }
    with open("/root/.openclaw/workspace-xiaoyan/output/audit_listening_explanation_results.json", "w", encoding="utf-8") as f:
        json.dump(output, f, ensure_ascii=False, indent=2)
    print(f"\n详细结果已保存到 output/audit_listening_explanation_results.json")


if __name__ == "__main__":
    main()
