#!/usr/bin/env python3
"""
Bitable 样例数据验证 — 核心互动类型

从飞书多维表格读取样例记录，模拟组件配置文本，运行生成流水线，验证输出JSON结构。
支持: core_reading_imageDrag / core_speaking_reply / core_speaking_inquiry / core_speaking_image
"""

import os
import sys
import json
import time
import logging

CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, CURRENT_PATH)

from feishu_client import read_bitable_records, list_bitable_tables
from generate_json import (
    generate_core_reading_imagedrag,
    generate_core_speaking_reply,
    generate_core_speaking_inquiry,
    generate_core_speaking_image,
    parse_core_imagedrag_fields,
    parse_core_speaking_fields,
    parse_core_speaking_inquiry_fields,
    parse_core_speaking_image_fields,
)
from llm_client import get_client
from html_report import generate_html_report

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("validate_v5")

# ── bitable 配置 ──
BITABLE_CONFIG = {
    "core_reading_imageDrag": {
        "name": "看图选词",
        "bitable_wiki": "MVo7wugWfimJPIkxhJCcqppFnyf",
        "db_table": "tblKl9CGmARjpw1O",
        # 预期的bitable字段名 → 组件配置section映射
        "config_fields": [
            "教研-导览配置",
            "教研-互动配置",
        ],
        "id_field": "ID",
        "expected_output_keys": {
            "taskInfo": ["cType", "cId", "title", "taskDesc", "sceneDesc", "key", "resourceMapping", "cDesc"],
            "questionGroup": None,  # list, 检查非空
            "studyInfo": ["learningPart", "closing"],
        },
    },
    "core_speaking_reply": {
        "name": "口语快答",
        "bitable_wiki": "TSwcw0nFmi21khkpUndchNMsn6f",
        "db_table": "tbl4Qg8d45O58Cqf",
        "config_fields": [
            "教研-用户视角任务信息",
            "教研-任务规则与NPC设定",
        ],
        "id_field": "ID",
        "expected_output_keys": {
            "taskInfo": ["cType", "cId", "title", "taskDesc", "sceneDesc", "key", "resourceMapping"],
            "dialogSetting": ["setting"],
            "dialogConfig": ["config"],
            "studyInfo": ["learningPart"],
        },
    },
    "core_speaking_inquiry": {
        "name": "口语妙问",
        "bitable_wiki": "GJUVwNSEkis3EXkrVj0ccbqdn8c",
        "db_table": "tbl1Q68oopST9Mel",
        "config_fields": [
            "教研-用户视角任务信息",
            "教研-任务规则与NPC设定",
        ],
        "id_field": "ID",
        "expected_output_keys": {
            "taskInfo": ["cType", "cId", "title", "taskDesc", "sceneDesc", "key", "resourceMapping"],
            "dialogSetting": ["setting"],
            "dialogConfig": ["config"],
            "studyInfo": ["learningPart"],
        },
    },
    "core_speaking_image": {
        "name": "看图说话",
        "bitable_wiki": "KBOXwzVHfin6ORkKbA3c3eWEnoh",
        "db_table": "tblvi5HF0uSU2GNo",
        "config_fields": [
            "教研-导览配置",
            "教研-对话配置",
        ],
        "id_field": "ID",
        "expected_output_keys": {
            "taskInfo": ["cType", "cId", "title", "taskDesc", "sceneDesc", "key", "resourceMapping", "cDesc"],
            "dialogConfig": ["config"],
            "imageInfo": ["questionImg"],
            "optionList": None,
            "questionList": None,
            "studyInfo": ["learningPart"],
        },
    },
}


def _extract_text(field_value):
    """从bitable字段值中提取文本（处理各种格式）"""
    if field_value is None:
        return ""
    if isinstance(field_value, str):
        return field_value
    if isinstance(field_value, list):
        # 富文本格式: [{"text": "xxx", "type": "text"}, ...]
        parts = []
        for item in field_value:
            if isinstance(item, dict):
                parts.append(item.get("text", ""))
            elif isinstance(item, str):
                parts.append(item)
        return "".join(parts)
    return str(field_value)


def build_teaching_config_from_record(record_fields, config_field_names):
    """从bitable记录字段构建teaching_config文本"""
    parts = []
    for field_name in config_field_names:
        value = _extract_text(record_fields.get(field_name, ""))
        if value.strip():
            parts.append(value.strip())
    return "\n\n".join(parts)


def validate_json_structure(data, expected_keys, label):
    """验证JSON输出包含预期的key"""
    issues = []
    if expected_keys is None:
        # 只需检查非空
        if not data:
            issues.append(f"{label}: 数据为空")
        return issues

    if not isinstance(data, dict):
        issues.append(f"{label}: 期望dict，实际为{type(data).__name__}")
        return issues

    for key in expected_keys:
        if key not in data:
            issues.append(f"{label}: 缺少key '{key}'")
    return issues


def validate_core_type(cType, num_records=1, record_index=0):
    """
    验证一种核心互动类型

    Args:
        cType: 核心互动类型标识
        num_records: 读取的记录数
        record_index: 使用第几条记录（0-based）
    """
    config = BITABLE_CONFIG[cType]
    logger.info(f"\n{'='*60}")
    logger.info(f"验证 {config['name']} ({cType})")
    logger.info(f"{'='*60}")

    # 1. 读取bitable记录
    logger.info(f"Step 1: 读取bitable记录 (wiki={config['bitable_wiki']}, table={config['db_table']})")
    try:
        records = read_bitable_records(config["bitable_wiki"], config["db_table"], page_size=num_records)
        if not records:
            logger.error("未读取到任何记录")
            return None
        logger.info(f"  读取到 {len(records)} 条记录")
    except Exception as e:
        logger.error(f"读取bitable失败: {e}")
        return None

    # 选择记录
    if record_index >= len(records):
        record_index = 0
    record = records[record_index]
    fields = record["fields"]

    # 打印可用字段名
    logger.info(f"  记录字段: {list(fields.keys())}")

    # 提取ID
    cId = _extract_text(fields.get(config["id_field"], ""))
    if not cId:
        cId = f"TEST_{cType}"
    logger.info(f"  使用记录: record_id={record['record_id']}, ID={cId}")

    # 2. 构建teaching_config
    logger.info(f"Step 2: 构建teaching_config")
    teaching_config = build_teaching_config_from_record(fields, config["config_fields"])
    if not teaching_config.strip():
        # 尝试拼接所有文本字段
        logger.warning(f"  配置字段为空，尝试拼接所有字段")
        all_parts = []
        for k, v in fields.items():
            text = _extract_text(v)
            if text.strip() and k != config["id_field"]:
                all_parts.append(f"【{k}】\n{text.strip()}")
        teaching_config = "\n\n".join(all_parts)
    logger.info(f"  teaching_config: {len(teaching_config)} 字符")
    if len(teaching_config) < 20:
        logger.error(f"  teaching_config过短，可能数据不完整")
        return None

    # 打印前500字符预览
    logger.info(f"  预览:\n{teaching_config[:500]}")

    # 3. 解析字段（预检查）
    logger.info(f"Step 3: 字段解析预检查")
    if cType == "core_reading_imageDrag":
        parsed = parse_core_imagedrag_fields(teaching_config)
    elif cType == "core_speaking_inquiry":
        parsed = parse_core_speaking_inquiry_fields(teaching_config)
    elif cType == "core_speaking_image":
        parsed = parse_core_speaking_image_fields(teaching_config)
    else:
        parsed = parse_core_speaking_fields(teaching_config)
    for k, v in parsed.items():
        logger.info(f"  {k}: {'✅' if v else '❌'} ({len(v)} chars)" if v else f"  {k}: ❌ (空)")

    # 4. 运行生成流水线
    logger.info(f"Step 4: 运行LLM生成流水线")
    llm_client = get_client()
    start = time.time()

    try:
        if cType == "core_reading_imageDrag":
            result = generate_core_reading_imagedrag(
                cId=cId,
                teaching_config=teaching_config,
                character_map={},
                llm_client=llm_client,
            )
        elif cType == "core_speaking_inquiry":
            result = generate_core_speaking_inquiry(
                cId=cId,
                teaching_config=teaching_config,
                character_map={},
                llm_client=llm_client,
            )
        elif cType == "core_speaking_image":
            result = generate_core_speaking_image(
                cId=cId,
                teaching_config=teaching_config,
                character_map={},
                llm_client=llm_client,
            )
        else:
            result = generate_core_speaking_reply(
                cId=cId,
                teaching_config=teaching_config,
                character_map={},
                llm_client=llm_client,
            )
    except Exception as e:
        logger.error(f"  生成失败: {e}")
        import traceback
        traceback.print_exc()
        return None

    elapsed = time.time() - start
    logger.info(f"  生成完成，耗时: {elapsed:.1f}s")

    # 5. 验证输出结构
    logger.info(f"Step 5: 验证输出JSON结构")
    all_issues = []
    for output_key, expected_keys in config["expected_output_keys"].items():
        data = result.get(output_key)
        if data is None:
            all_issues.append(f"{output_key}: 输出为None")
            continue
        issues = validate_json_structure(data, expected_keys, output_key)
        all_issues.extend(issues)
        if not issues:
            if isinstance(data, dict):
                logger.info(f"  ✅ {output_key}: keys={list(data.keys())}")
            elif isinstance(data, list):
                logger.info(f"  ✅ {output_key}: {len(data)} items")
        else:
            for issue in issues:
                logger.warning(f"  ⚠️  {issue}")

    if all_issues:
        logger.warning(f"\n共 {len(all_issues)} 个问题:")
        for issue in all_issues:
            logger.warning(f"  - {issue}")
    else:
        logger.info(f"\n✅ 所有输出JSON结构验证通过!")

    # 6. 构建pipeline report格式（用于HTML报告）
    report_entry = {
        "cId": cId,
        "cType": cType,
        "type_name": config["name"],
        "category": "core",
        "status": "generated",
        "jsonData": result.get("taskInfo", {}),
        "kpInfo": result.get("kpInfo"),
    }
    for k in ["taskInfo", "materialInfo", "flowInfo", "studyInfo",
              "questionGroup",
              "dialogSetting", "dialogConfig",
              "imageInfo", "optionList", "questionList",
              "preDialog", "dialogList",
              "intermediate"]:
        if result.get(k) is not None:
            report_entry[k] = result[k]

    return {
        "cId": cId,
        "cType": cType,
        "result": result,
        "issues": all_issues,
        "elapsed": elapsed,
        "report_entry": report_entry,
    }


def main():
    import argparse
    parser = argparse.ArgumentParser(description="Bitable样例数据验证 - 核心互动类型")
    parser.add_argument("--type", choices=["imageDrag", "speaking", "inquiry", "image", "all"], default="all",
                        help="验证类型: imageDrag/speaking/inquiry/image/all")
    parser.add_argument("--records", type=int, default=2, help="读取记录数")
    parser.add_argument("--index", type=int, default=0, help="使用第几条记录")
    parser.add_argument("--html", action="store_true", help="生成HTML报告")
    parser.add_argument("--output-json", type=str, help="输出结果到JSON文件")
    args = parser.parse_args()

    types_to_validate = []
    if args.type in ("imageDrag", "all"):
        types_to_validate.append("core_reading_imageDrag")
    if args.type in ("speaking", "all"):
        types_to_validate.append("core_speaking_reply")
    if args.type in ("inquiry", "all"):
        types_to_validate.append("core_speaking_inquiry")
    if args.type in ("image", "all"):
        types_to_validate.append("core_speaking_image")

    all_results = []
    all_report_entries = []

    for cType in types_to_validate:
        result = validate_core_type(cType, num_records=args.records, record_index=args.index)
        if result:
            all_results.append(result)
            all_report_entries.append(result["report_entry"])

    # 汇总
    print(f"\n{'='*60}")
    print(f"验证汇总")
    print(f"{'='*60}")
    for r in all_results:
        status = "✅ PASS" if not r["issues"] else f"⚠️  {len(r['issues'])} issues"
        print(f"  {r['cType']} (cId={r['cId']}): {status} ({r['elapsed']:.1f}s)")

    # 输出JSON
    if args.output_json:
        output = {}
        for r in all_results:
            output[r["cType"]] = {
                "cId": r["cId"],
                "issues": r["issues"],
                "elapsed": r["elapsed"],
                "result": {k: v for k, v in r["result"].items() if k != "intermediate"},
            }
        os.makedirs(os.path.dirname(args.output_json) or ".", exist_ok=True)
        with open(args.output_json, "w") as f:
            json.dump(output, f, ensure_ascii=False, indent=2)
        print(f"\n结果已保存: {args.output_json}")

    # 生成HTML报告
    if args.html and all_report_entries:
        from llm_client import DEFAULT_MODEL
        report = {
            "wiki_url": "bitable_validation",
            "title": "核心互动验证",
            "total_components": len(all_report_entries),
            "success": sum(1 for r in all_results if not r["issues"]),
            "failed": sum(1 for r in all_results if r["issues"]),
            "skipped": 0,
            "results": all_report_entries,
            "errors": [],
        }
        html_path = generate_html_report(report, model_name=DEFAULT_MODEL, wiki_token="v6_validation")
        print(f"\nHTML报告: {html_path}")

    # 退出码
    total_issues = sum(len(r["issues"]) for r in all_results)
    sys.exit(1 if total_issues > 0 else 0)


if __name__ == "__main__":
    main()
