import argparse
import asyncio
import json
import os
import re
import statistics
import time
from datetime import datetime, timedelta
from typing import Any, Dict, List

try:
    from dotenv import load_dotenv
except Exception:
    def load_dotenv():
        return None

# 与现有评估脚本保持一致的导入方式
from app.core.memory.evaluation.common.metrics import (
    avg_context_tokens,
    jaccard,
    latency_stats,
)
from app.core.memory.evaluation.common.metrics import f1_score as common_f1
from app.core.memory.evaluation.dialogue_queries import SEARCH_ENTITIES_BY_NAME
from app.core.memory.llm_tools.openai_embedder import OpenAIEmbedderClient
from app.core.memory.utils.config.definitions import (
    PROJECT_ROOT,
    SELECTED_EMBEDDING_ID,
    SELECTED_LLM_ID,
)
from app.core.memory.utils.llm.llm_utils import MemoryClientFactory
from app.core.models.base import RedBearModelConfig
from app.db import get_db_context
from app.repositories.neo4j.graph_search import search_graph, search_graph_by_embedding
from app.repositories.neo4j.neo4j_connector import Neo4jConnector
from app.services.memory_config_service import MemoryConfigService

try:
    from app.core.memory.evaluation.common.metrics import exact_match
except Exception:
    # 兜底：简单的大小写不敏感比较
    def exact_match(pred: str, ref: str) -> bool:
        return str(pred).strip().lower() == str(ref).strip().lower()


def load_dataset_any(path: str) -> List[Dict[str, Any]]:
    """健壮地加载数据集（兼容 list 或多段 JSON）。"""
    with open(path, "r", encoding="utf-8") as f:
        s = f.read().strip()
    try:
        obj = json.loads(s)
        if isinstance(obj, list):
            return obj
        elif isinstance(obj, dict):
            return [obj]
    except json.JSONDecodeError:
        pass
    dec = json.JSONDecoder()
    idx = 0
    items: List[Dict[str, Any]] = []
    while idx < len(s):
        while idx < len(s) and s[idx].isspace():
            idx += 1
        if idx >= len(s):
            break
        try:
            obj, end = dec.raw_decode(s, idx)
            if isinstance(obj, list):
                for it in obj:
                    if isinstance(it, dict):
                        items.append(it)
            elif isinstance(obj, dict):
                items.append(obj)
            idx = end
        except json.JSONDecodeError:
            nl = s.find("\n", idx)
            if nl == -1:
                break
            idx = nl + 1
    return items


def is_chinese_text(s: str) -> bool:
    return bool(re.search(r"[\u4e00-\u9fff]", s or ""))


def extract_candidate_options(question: str) -> List[str]:
    """从问题中提取候选选项（A-or-B 类问题）。"""
    q = (question or "").strip()
    options: List[str] = []

    # 1) 引号包裹的片段
    for pat in [r"'([^']+)'", r'\"([^\"]+)\"', r'“([^”]+)”', r'‘([^’]+)’']:
        for m in re.findall(pat, q):
            val = (m or "").strip()
            if val:
                options.append(val)

    # 2) or/还是/或者 连接词
    if len(options) < 2:
        pats = [
            r"([^,;，；]+?)\s+or\s+([^,;，；\?\.!.。！]+)",
            r"([^,;，；]+?)\s+还是\s+([^,;，；\?\.!.。！]+)",
            r"([^,;，；]+?)\s+或者\s+([^,;，；\?\.!.。！]+)",
        ]
        for pat in pats:
            matches = list(re.finditer(pat, q, flags=re.IGNORECASE))
            if matches:
                m = matches[-1]
                cand1 = m.group(1).strip().strip("?？.,，;； ")
                cand2 = m.group(2).strip().strip("?？.,，;； ")
                options.extend([cand1, cand2])
                break

    # 去重
    seen = set()
    uniq: List[str] = []
    for o in options:
        o2 = o.strip()
        key = o2.lower() if not is_chinese_text(o2) else o2
        if o2 and key not in seen:
            uniq.append(o2)
            seen.add(key)
    return uniq


def extract_time_entities(text: str) -> List[Dict[str, Any]]:
    """增强时间实体提取，专门用于时间推理问题"""
    time_entities = []

    # 日期模式
    date_patterns = [
        (r'\b(\d{4})-(\d{1,2})-(\d{1,2})\b', 'date'),  # YYYY-MM-DD
        (r'\b(\d{1,2})月(\d{1,2})日\b', 'date'),  # 中文日期
        (r'\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2}),?\s+(\d{4})?', 'date'),  # 英文月份
        (r'\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2}),?\s+(\d{4})?', 'date'),  # 英文月份缩写
    ]

    # 时间间隔模式
    duration_patterns = [
        (r'(\d+)\s*天', 'days'),
        (r'(\d+)\s*周', 'weeks'),
        (r'(\d+)\s*个月', 'months'),
        (r'(\d+)\s*年', 'years'),
        (r'(\d+)\s*days?', 'days'),
        (r'(\d+)\s*weeks?', 'weeks'),
        (r'(\d+)\s*months?', 'months'),
        (r'(\d+)\s*years?', 'years'),
    ]

    # 事件时间关系模式
    temporal_relation_patterns = [
        (r'(之前|以前|前)\s*(\d+)\s*天', 'days_before'),
        (r'(之后|以后|后)\s*(\d+)\s*天', 'days_after'),
        (r'(\d+)\s*天\s*(之前|以前|前)', 'days_before'),
        (r'(\d+)\s*天\s*(之后|以后|后)', 'days_after'),
        (r'(\d+)\s*days?\s*(before|ago)', 'days_before'),
        (r'(\d+)\s*days?\s*(after|later)', 'days_after'),
    ]

    # 提取日期
    for pattern, entity_type in date_patterns:
        matches = re.finditer(pattern, text, re.IGNORECASE)
        for match in matches:
            time_entities.append({
                'text': match.group(),
                'type': entity_type,
                'start': match.start(),
                'end': match.end()
            })

    # 提取时间间隔
    for pattern, entity_type in duration_patterns:
        matches = re.finditer(pattern, text, re.IGNORECASE)
        for match in matches:
            time_entities.append({
                'text': match.group(),
                'type': entity_type,
                'value': int(match.group(1)),
                'start': match.start(),
                'end': match.end()
            })

    # 提取时间关系
    for pattern, entity_type in temporal_relation_patterns:
        matches = re.finditer(pattern, text, re.IGNORECASE)
        for match in matches:
            time_entities.append({
                'text': match.group(),
                'type': entity_type,
                'value': int(match.group(2)) if match.groups() >= 2 else int(match.group(1)),
                'start': match.start(),
                'end': match.end()
            })

    return time_entities


def calculate_time_difference(date1: str, date2: str) -> int:
    """计算两个日期之间的天数差"""
    try:
        # 解析日期格式
        def parse_date(date_str: str) -> datetime:
            # 尝试多种日期格式
            formats = [
                '%Y-%m-%d',
                '%m月%d日',
                '%B %d, %Y',
                '%b %d, %Y',
                '%Y年%m月%d日'
            ]

            for fmt in formats:
                try:
                    return datetime.strptime(date_str, fmt)
                except ValueError:
                    continue

            # 如果都无法解析，返回当前日期
            return datetime.now()

        d1 = parse_date(date1)
        d2 = parse_date(date2)

        # 计算天数差（绝对值）
        return abs((d2 - d1).days)
    except Exception:
        return -1  # 表示计算失败


def _extract_cn_tokens(text: str) -> List[str]:
    """中文关键词提取（短语级，含数词/日期/常见领域词）"""
    if not text:
        return []
    t = str(text)
    # 去掉常见功能词（粗略，不依赖分词库）
    stop_words = [
        "我","我们","你","他","她","它","这","那","哪","一个","一次","一些","什么","怎么","是否","吗","呢",
        "很","更","最","已经","正在","将要","马上","尽快","最近","关于","有关","以及","并且","或者","还是",
        "因为","所以","如果","但是","而且","然后","之后","之前","同时","另外","并","但","却","被","把","让","给",
        "和","与","跟","及","还有","就","都","在","对","对于","的","了","着","过","到","于","从","以","为","向","至","是"
    ]
    for sw in stop_words:
        t = t.replace(sw, " ")
    # 去标点
    t = re.sub(r"[，。！？、；：,.!?;:\"'（）()［］\[\]\-—…·]", " ", t)
    # 基础中文片段（>=2）
    base = re.findall(r"[\u4e00-\u9fff]{2,}", t)
    # 特殊组合：第X次XXXX
    specials = re.findall(r"第[一二三四五六七八九十]+次[\u4e00-\u9fff]{2,6}", text)
    # 日期与数字
    dates = re.findall(r"\d{4}年\d{1,2}月\d{1,2}日|\d{1,2}月\d{1,2}日|\d{4}-\d{1,2}-\d{1,2}", text)
    numbers = re.findall(r"\b\d+\b", text)

    generic = {"建议","推荐","帮助","提升","技能","有效","团队","参与度","喜欢","开始"}
    tokens: List[str] = specials + base + dates + numbers
    uniq: List[str] = []
    seen = set()
    for tok in tokens:
        tok2 = tok.strip()
        if len(tok2) < 2 or len(tok2) > 6:
            continue
        if tok2 in generic:
            continue
        if tok2 not in seen:
            uniq.append(tok2)
            seen.add(tok2)
    # 排除常见疑问型短语
    blacklist_exact = {"是什么","多少","多少天","哪个","哪些","之间","先","后","之前","之后"}
    uniq2: List[str] = [u for u in uniq if u not in blacklist_exact]
    return uniq2[:12]


def generate_query_keywords_cn(question: str) -> List[str]:
    """增强版关键词提取，特别关注技术术语和专有名词"""
    if not question:
        return []

    # 提取专有名词（带引号的内容）
    quoted_terms = re.findall(r'["""]([^"""]+)["""]', question)

    # 提取技术术语（中英文混合）
    tech_terms = re.findall(r'[A-Z][a-zA-Z]+\s+[A-Z][a-zA-Z]+|[A-Za-z]+[\u4e00-\u9fff]+|[\u4e00-\u9fff]+[A-Za-z]+', question)

    # 提取核心名词短语
    core_nouns = re.findall(r'[\u4e00-\u9fff]{2,5}系统|[\u4e00-\u9fff]{2,5}管理|[\u4e00-\u9fff]{2,5}分析|[\u4e00-\u9fff]{2,5}工作坊|[\u4e00-\u9fff]{2,5}研讨会', question)

    # 基础中文片段
    base_tokens = _extract_cn_tokens(question)

    # 特定领域关键词增强
    domain_keywords = []
    # GPS相关
    if any(term in question for term in ["GPS", "导航", "定位系统", "系统运行"]):
        domain_keywords.extend(["GPS", "导航系统", "定位", "系统故障", "功能异常"])
    # 活动相关
    if any(term in question for term in ["工作坊", "研讨会", "网络研讨会", "活动"]):
        domain_keywords.extend(["工作坊", "研讨会", "参加", "参与", "活动"])
    # 时间顺序相关
    if any(term in question for term in ["先", "后", "第一个", "之前", "首先"]):
        domain_keywords.extend(["先", "后", "之前", "之后", "第一次", "首先"])
    # 设备相关
    if any(term in question for term in ["设备", "手机", "电脑", "笔记本电脑"]):
        domain_keywords.extend(["设备", "手机", "电脑", "笔记本电脑", "购买"])

    # 合并并去重
    all_tokens = quoted_terms + tech_terms + core_nouns + base_tokens + domain_keywords
    seen = set()
    final_tokens = []

    for token in all_tokens:
        token = token.strip()
        if len(token) >= 2 and token not in seen:
            final_tokens.append(token)
            seen.add(token)

    return final_tokens[:8]


def smart_context_selection(contexts: List[str], question: str, max_chars: int = 4000) -> str:
    """增强版上下文选择：特别优化技术术语和精确匹配"""
    if not contexts:
        return ""

    # 检测是否为时间推理问题
    is_temporal_question = any(keyword in question.lower() for keyword in
                              ['days', 'day', 'before', 'after', 'first', '先后', '顺序', '间隔', '多久', '多少天'])

    # 提取时间实体从问题中
    question_time_entities = extract_time_entities(question)

    # 提取关键技术实体
    key_entities = []
    # GPS相关
    if any(term in question for term in ["GPS", "导航", "定位系统", "系统运行"]):
        key_entities.extend(["GPS", "导航", "定位", "系统", "功能", "问题", "故障"])
    # 活动相关
    if any(term in question for term in ["工作坊", "研讨会", "网络研讨会", "活动"]):
        key_entities.extend(["工作坊", "研讨会", "参加", "参与", "活动", "时间"])
    # 时间顺序相关
    if any(term in question for term in ["先", "后", "第一个", "之前", "首先"]):
        key_entities.extend(["先", "后", "之前", "之后", "第一次", "首先"])

    # 英文关键词（去停用词）
    question_lower = question.lower()
    stop_words = {
        'what','when','where','who','why','how','did','do','does','is','are','was','were',
        'the','a','an','and','or','but','many','which','first'
    }
    eng_words = [w for w in set(re.findall(r'\b\w+\b', question_lower))
                if w not in stop_words and len(w) > 2]

    # 中文片段与候选选项
    cn_tokens = generate_query_keywords_cn(question)
    options = extract_candidate_options(question)

    # 时间推理问题的特殊处理
    if is_temporal_question:
        # 为时间问题添加时间相关关键词
        time_keywords = ['天', '日', '月', '年', 'before', 'after', 'days', 'first', '先后']
        eng_words = [w for w in eng_words if w not in ['days', 'first']]  # 避免重复
        cn_tokens.extend([kw for kw in time_keywords if kw not in cn_tokens])

        # 限制关键词数量，优先时间相关
        tokens = time_keywords[:2] + key_entities[:3] + cn_tokens[:2] + eng_words[:1] + options[:1]
    else:
        # 常规问题处理，优先关键技术实体
        tokens = key_entities[:4] + cn_tokens[:3] + options[:2] + eng_words[:1]

    # 去重
    seen = set()
    final_tokens: List[str] = []
    for t in tokens:
        t2 = t.strip()
        if t2 and t2 not in seen:
            final_tokens.append(t2)
            seen.add(t2)

    scored_contexts: List[tuple[float, str]] = []

    # 关键技术实体权重映射
    key_entity_weights = {
        "GPS": 3.0, "导航": 2.5, "系统": 2.0, "功能": 2.0, "问题": 2.0, "故障": 2.5,
        "工作坊": 2.5, "研讨会": 2.5, "参加": 2.0, "参与": 2.0,
        "先": 2.0, "后": 2.0, "之前": 2.0, "之后": 2.0, "第一次": 2.5
    }

    # 时间推理问题的权重映射
    temporal_weight_map = {
        "天": 2.0, "日": 2.0, "月": 1.8, "年": 1.8, "days": 2.0,
        "before": 1.5, "after": 1.5, "first": 1.5, "先后": 1.5
    }

    # 常规问题的权重映射
    normal_weight_map = {
        "问题": 2.0, "故障": 2.0, "异常": 1.8, "不正常": 1.8, "坏了": 1.8,
        "系统": 1.3, "GPS": 1.5, "保养": 1.4, "设备": 1.2, "模块": 1.2, "功能": 1.1
    }

    # 合并权重映射
    weight_map = {**normal_weight_map, **temporal_weight_map, **key_entity_weights}

    for i, context in enumerate(contexts):
        context_str = str(context)
        lines = re.split(r'[\r\n]+', context_str)
        hit_lines: List[str] = []
        kw_hits: float = 0.0
        time_entity_count = 0
        key_entity_hits = 0

        for line in lines:
            ln = line.strip()
            if not ln:
                continue

            has_keyword = False
            # 关键词匹配
            for tok in final_tokens:
                if tok and tok in ln:
                    w = weight_map.get(tok, 1.0)
                    hit_count = ln.count(tok)
                    kw_hits += hit_count * w
                    # 关键技术实体额外奖励
                    if tok in key_entity_weights:
                        key_entity_hits += hit_count
                    has_keyword = True

            # 时间实体检测（特别针对时间推理问题）
            if is_temporal_question:
                time_entities = extract_time_entities(ln)
                time_entity_count += len(time_entities)
                if time_entities:
                    has_keyword = True

            # 精确匹配奖励（完整问题关键词出现在上下文中）
            for q_word in question.split():
                if len(q_word) > 3 and q_word in ln:
                    kw_hits += 0.5  # 精确匹配奖励

            if has_keyword:
                # 对于包含关键信息的行，保留完整行
                hit_lines.append(ln)

        snippet = "\n".join(hit_lines) if hit_lines else context_str.strip()

        # 限制单段长度，但对包含关键信息的上下文稍微放宽限制
        max_snippet_len = 600 if (key_entity_hits > 0 or time_entity_count > 0) else 500
        if len(snippet) > max_snippet_len:
            snippet = snippet[:max_snippet_len]

        # 评分逻辑
        has_number = 1 if re.search(r'\d', snippet) else 0
        has_date = 1 if (re.search(r'\b\d{4}-\d{1,2}-\d{1,2}\b', snippet) or
                        re.search(r'\d{1,2}月\d{1,2}日', snippet)) else 0

        # 关键技术实体奖励
        key_entity_bonus = key_entity_hits * 1.0

        # 时间推理问题的特殊评分
        if is_temporal_question:
            time_bonus = time_entity_count * 2.0  # 时间实体奖励
            temporal_coherence = 3 if (has_date and time_entity_count >= 2) else 0
        else:
            time_bonus = 0
            temporal_coherence = 0

        length_bonus = 5 if 50 < len(snippet) < 1000 else (2 if len(snippet) >= 1000 else 0)
        pos_bonus = 3 if i < 3 else 0

        score = (kw_hits * 0.8 + (has_number + has_date) * 1.5 +
                length_bonus + pos_bonus + time_bonus + temporal_coherence + key_entity_bonus)

        scored_contexts.append((score, snippet))

    # 选择累计至总字符预算
    scored_contexts.sort(key=lambda x: x[0], reverse=True)
    selected: List[str] = []
    total_chars = 0

    for score, snippet in scored_contexts:
        if total_chars + len(snippet) <= max_chars:
            selected.append(snippet)
            total_chars += len(snippet)
        else:
            if not selected and len(snippet) > max_chars:
                selected.append(snippet[:max_chars])
            break

    final_context = "\n\n".join(selected)

    # 对于时间推理问题，添加时间计算提示
    if is_temporal_question and question_time_entities:
        time_prompt = "\n\n[时间推理提示：请仔细分析上述上下文中的日期和时间关系，计算时间间隔或确定事件顺序]"
        if total_chars + len(time_prompt) <= max_chars:
            final_context += time_prompt

    return final_context


# 通过别名匹配进行实体关键词检索（多token合并）
async def _search_entities_by_aliases(connector: Neo4jConnector, tokens: List[str], group_id: str | None, limit: int) -> List[Dict[str, Any]]:
    results: List[Dict[str, Any]] = []
    try:
        for tok in tokens:
            rows = await connector.execute_query(SEARCH_ENTITIES_BY_NAME, q=tok, group_id=group_id, limit=limit)
            if rows:
                results.extend(rows)
    except Exception:
        pass

    # 按 name 去重
    deduped: List[Dict[str, Any]] = []
    seen = set()
    for r in results:
        k = str(r.get("name", ""))
        if k and k not in seen:
            deduped.append(r)
            seen.add(k)
    return deduped


# 通过对话/陈述中的entity_ids反查实体名称
_FETCH_ENTITIES_BY_IDS = """
MATCH (e:ExtractedEntity)
WHERE e.id IN $ids AND ($group_id IS NULL OR e.group_id = $group_id)
RETURN e.id AS id, e.name AS name, e.group_id AS group_id, e.entity_type AS entity_type
"""

async def _fetch_entities_by_ids(connector: Neo4jConnector, ids: List[str], group_id: str | None) -> List[Dict[str, Any]]:
    if not ids:
        return []
    try:
        rows = await connector.execute_query(_FETCH_ENTITIES_BY_IDS, ids=list({i for i in ids if i}), group_id=group_id)
        return rows or []
    except Exception:
        return []


# 增强的时间实体检索
_TIME_ENTITY_SEARCH = """
MATCH (e:ExtractedEntity)
WHERE e.entity_type CONTAINS "TIME" OR e.entity_type CONTAINS "DATE" OR e.name =~ $date_pattern
AND ($group_id IS NULL OR e.group_id = $group_id)
RETURN e.id AS id, e.name AS name, e.group_id AS group_id, e.entity_type AS entity_type
LIMIT $limit
"""

async def _search_time_entities(connector: Neo4jConnector, group_id: str | None, limit: int = 5) -> List[Dict[str, Any]]:
    """专门搜索时间相关的实体"""
    try:
        date_pattern = r".*\d{4}.*|.*\d{1,2}月\d{1,2}日.*"
        rows = await connector.execute_query(_TIME_ENTITY_SEARCH,
                                           date_pattern=date_pattern,
                                           group_id=group_id,
                                           limit=limit)
        return rows or []
    except Exception:
        return []


# 技术术语专门检索
async def _search_tech_terms(connector: Neo4jConnector, question: str, group_id: str | None, limit: int = 3) -> List[Dict[str, Any]]:
    """专门搜索技术术语相关的实体"""
    tech_entities = []
    try:
        # GPS相关
        if any(term in question for term in ["GPS", "导航", "定位系统"]):
            gps_rows = await connector.execute_query(SEARCH_ENTITIES_BY_NAME, q="GPS", group_id=group_id, limit=limit)
            if gps_rows:
                tech_entities.extend(gps_rows)

        # 活动相关
        if any(term in question for term in ["工作坊", "研讨会", "网络研讨会"]):
            workshop_rows = await connector.execute_query(SEARCH_ENTITIES_BY_NAME, q="工作坊", group_id=group_id, limit=limit)
            if workshop_rows:
                tech_entities.extend(workshop_rows)

        # 时间顺序相关
        if any(term in question for term in ["先", "后", "第一个"]):
            time_rows = await connector.execute_query(SEARCH_ENTITIES_BY_NAME, q="第一次", group_id=group_id, limit=limit)
            if time_rows:
                tech_entities.extend(time_rows)

    except Exception:
        pass

    return tech_entities


# 中英相对时间解析：today/昨天/上周/3天后 等简单归一化为日期
def _resolve_relative_times_cn_en(text: str, anchor: datetime) -> str:
    t = str(text) if text is not None else ""
    # 英文 today/yesterday/tomorrow
    t = re.sub(r"\btoday\b", anchor.date().isoformat(), t, flags=re.IGNORECASE)
    t = re.sub(r"\byesterday\b", (anchor - timedelta(days=1)).date().isoformat(), t, flags=re.IGNORECASE)
    t = re.sub(r"\btomorrow\b", (anchor + timedelta(days=1)).date().isoformat(), t, flags=re.IGNORECASE)

    # 英文 X days ago / in X days
    def _ago_repl(m: re.Match[str]) -> str:
        n = int(m.group(1))
        return (anchor - timedelta(days=n)).date().isoformat()
    def _in_repl(m: re.Match[str]) -> str:
        n = int(m.group(1))
        return (anchor + timedelta(days=n)).date().isoformat()
    t = re.sub(r"\b(\d+)\s+days\s+ago\b", _ago_repl, t, flags=re.IGNORECASE)
    t = re.sub(r"\bin\s+(\d+)\s+days\b", _in_repl, t, flags=re.IGNORECASE)
    t = re.sub(r"\blast\s+week\b", (anchor - timedelta(days=7)).date().isoformat(), t, flags=re.IGNORECASE)
    t = re.sub(r"\bnext\s+week\b", (anchor + timedelta(days=7)).date().isoformat(), t, flags=re.IGNORECASE)

    # 中文 今天/昨天/明天
    t = re.sub(r"今天", anchor.date().isoformat(), t)
    t = re.sub(r"昨日|昨天", (anchor - timedelta(days=1)).date().isoformat(), t)
    t = re.sub(r"明天", (anchor + timedelta(days=1)).date().isoformat(), t)
    # 中文 X天前 / X天后
    t = re.sub(r"(\d+)天前", lambda m: (anchor - timedelta(days=int(m.group(1)))).date().isoformat(), t)
    t = re.sub(r"(\d+)天后", lambda m: (anchor + timedelta(days=int(m.group(1)))).date().isoformat(), t)
    # 中文 上周 / 下周（近似7天）
    t = re.sub(r"上周", (anchor - timedelta(days=7)).date().isoformat(), t)
    t = re.sub(r"下周", (anchor + timedelta(days=7)).date().isoformat(), t)
    # 中文 月日（无年份）补全年份
    def _md_repl(m: re.Match[str]) -> str:
        mon = int(m.group(1)); day = int(m.group(2))
        return f"{anchor.year}-{mon:02d}-{day:02d}"
    t = re.sub(r"(\d{1,2})月(\d{1,2})日", _md_repl, t)
    return t


async def run_longmemeval_test(
    sample_size: int = 3,
    group_id: str = "longmemeval_zh_bak_2",
    search_limit: int = 8,
    context_char_budget: int = 4000,
    llm_temperature: float = 0.0,
    llm_max_tokens: int = 16,
    search_type: str = "hybrid",
    data_path: str | None = None,
    start_index: int = 0,
) -> Dict[str, Any]:
    """LongMemEval 评估测试：增强技术术语检索能力"""

    # 数据路径
    if not data_path:
        # 固定使用中文数据集：data/longmemeval_oracle_zh.json
        zh_proj = os.path.join(PROJECT_ROOT, "data", "longmemeval_oracle_zh.json")
        zh_cwd = os.path.join(os.getcwd(), "data", "longmemeval_oracle_zh.json")
        if os.path.exists(zh_proj):
            data_path = zh_proj
        elif os.path.exists(zh_cwd):
            data_path = zh_cwd
        else:
            raise FileNotFoundError("未找到数据集: data/longmemeval_oracle_zh.json，请确保其存在于项目根目录或当前工作目录的 data 目录下。")

    qa_list: List[Dict[str, Any]] = load_dataset_any(data_path)
    # 支持评估全部样本：当 sample_size <= 0 时，取从 start_index 到末尾
    if sample_size is None or sample_size <= 0:
        items = qa_list[start_index:]
    else:
        items = qa_list[start_index:start_index + sample_size]

    # 初始化组件 - 使用异步LLM客户端
    with get_db_context() as db:
        factory = MemoryClientFactory(db)
        llm_client = factory.get_llm_client(SELECTED_LLM_ID)
    connector = Neo4jConnector()
    with get_db_context() as db:
        config_service = MemoryConfigService(db)
        cfg_dict = config_service.get_embedder_config(SELECTED_EMBEDDING_ID)
    embedder = OpenAIEmbedderClient(
        model_config=RedBearModelConfig.model_validate(cfg_dict)
    )

    # 指标收集
    latencies_llm: List[float] = []
    latencies_search: List[float] = []
    per_query_context_counts: List[int] = []
    per_query_context_avg_tokens: List[float] = []
    per_query_context_chars: List[int] = []

    type_correct: Dict[str, List[float]] = {}
    type_f1: Dict[str, List[float]] = {}
    type_jacc: Dict[str, List[float]] = {}

    samples: List[Dict[str, Any]] = []
    # 统计重复的上下文预览（跨样本），便于诊断"相同上下文"问题
    preview_counter: Dict[str, int] = {}

    try:
        for item in items:
            question = item.get("question", "")
            reference = item.get("answer", "")
            qtype = item.get("question_type") or item.get("type", "unknown")

            print(f"\n=== 处理问题: {question} ===")

            # 检测问题类型
            is_temporal = any(keyword in question.lower() for keyword in
                             ['days', 'day', 'before', 'after', 'first', '先后', '顺序', '间隔', '多久', '多少天'])

            # 检索
            t0 = time.time()
            contexts_all: List[str] = []
            dialogs, statements, entities = [], [], []

            try:
                if search_type == "embedding":
                    search_results = await search_graph_by_embedding(
                        connector=connector,
                        embedder_client=embedder,
                        query_text=question,
                        group_id=group_id,
                        limit=search_limit,
                        include=["dialogues", "statements", "entities"],
                    )
                    dialogs = search_results.get("dialogues", [])
                    statements = search_results.get("statements", [])
                    entities = search_results.get("entities", [])

                    for d in dialogs:
                        content = str(d.get("content", "")).strip()
                        if content:
                            contexts_all.append(content)
                    for s in statements:
                        stmt_text = str(s.get("statement", "")).strip()
                        if stmt_text:
                            contexts_all.append(stmt_text)
                    # 实体摘要（最多3个）
                    scored = [e for e in entities if e.get("score") is not None]
                    top_entities = sorted(scored, key=lambda x: x.get("score", 0), reverse=True)[:3] if scored else entities[:3]
                    if top_entities:
                        summary_lines = []
                        for e in top_entities:
                            name = str(e.get("name", "")).strip()
                            etype = str(e.get("entity_type", "")).strip()
                            score = e.get("score")
                            if name:
                                meta = []
                                if etype:
                                    meta.append(f"type={etype}")
                                if isinstance(score, (int, float)):
                                    meta.append(f"score={score:.3f}")
                                summary_lines.append(f"EntitySummary: {name}{(' [' + '; '.join(meta) + ']') if meta else ''}")
                        if summary_lines:
                            contexts_all.append("\n".join(summary_lines))

                elif search_type == "keyword":
                    search_results = await search_graph(
                        connector=connector,
                        q=question,
                        group_id=group_id,
                        limit=search_limit,
                    )
                    dialogs = search_results.get("dialogues", [])
                    statements = search_results.get("statements", [])
                    entities = search_results.get("entities", [])

                    for d in dialogs:
                        content = str(d.get("content", "")).strip()
                        if content:
                            contexts_all.append(content)
                    for s in statements:
                        stmt_text = str(s.get("statement", "")).strip()
                        if stmt_text:
                            contexts_all.append(stmt_text)
                    if entities:
                        entity_names = [str(e.get("name", "")).strip() for e in entities[:5] if e.get("name")]
                        if entity_names:
                            contexts_all.append(f"EntitySummary: {', '.join(entity_names)}")

                else:  # hybrid（增强版：特别优化技术术语检索）
                    emb_dialogs, emb_statements, emb_entities = [], [], []
                    kw_dialogs, kw_statements, kw_entities = [], [], []

                    # 1) 嵌入检索
                    try:
                        emb_res = await search_graph_by_embedding(
                            connector=connector,
                            embedder_client=embedder,
                            query_text=question,
                            group_id=group_id,
                            limit=search_limit,
                            include=["dialogues", "statements", "entities"],
                        )
                        if isinstance(emb_res, dict):
                            emb_dialogs = emb_res.get("dialogues", []) or []
                            emb_statements = emb_res.get("statements", []) or []
                            emb_entities = emb_res.get("entities", []) or []
                    except Exception as e:
                        print(f"⚠️ 嵌入检索失败，将继续进行关键词检索: {e}")

                    # 2) 关键词检索（增强版）
                    try:
                        kw_res = await search_graph(
                            connector=connector,
                            q=question,
                            group_id=group_id,
                            limit=search_limit,
                        )
                        if isinstance(kw_res, dict):
                            kw_dialogs = kw_res.get("dialogues", []) or []
                            kw_statements = kw_res.get("statements", []) or []
                            kw_entities = kw_res.get("entities", []) or []

                            # 技术术语专门检索
                            tech_entities = await _search_tech_terms(connector, question, group_id, search_limit//2)
                            if tech_entities:
                                kw_entities.extend(tech_entities)

                            # 时间推理问题的特殊处理
                            if is_temporal:
                                # 专门搜索时间实体
                                time_entities = await _search_time_entities(connector, group_id, search_limit//2)
                                if time_entities:
                                    kw_entities.extend(time_entities)
                                # 添加时间相关关键词检索
                                time_keywords = ['天', '日', '月', '年', 'before', 'after', 'first']
                                for tk in time_keywords:
                                    try:
                                        time_res = await search_graph(
                                            connector=connector,
                                            q=tk,
                                            group_id=group_id,
                                            limit=2,
                                        )
                                        if isinstance(time_res, dict):
                                            kw_dialogs.extend(time_res.get("dialogues", []) or [])
                                            kw_statements.extend(time_res.get("statements", []) or [])
                                    except Exception:
                                        pass

                            # 中文关键词拆分后做别名匹配
                            cn_tokens = generate_query_keywords_cn(question)  # 使用增强版关键词提取
                            alias_entities = await _search_entities_by_aliases(connector, cn_tokens, group_id, search_limit)
                            if alias_entities:
                                kw_entities.extend(alias_entities)

                            # 从对话/陈述中的 entity_ids 反查实体
                            ids = []
                            try:
                                for d in kw_dialogs:
                                    ids.extend(d.get("entity_ids", []) or [])
                                for s in kw_statements:
                                    ids.extend(s.get("entity_ids", []) or [])
                            except Exception:
                                pass
                            if ids:
                                id_entities = await _fetch_entities_by_ids(connector, ids, group_id)
                                if id_entities:
                                    kw_entities.extend(id_entities)

                            # 多关键词检索（使用增强版关键词）
                            try:
                                eng_words = [w for w in set(re.findall(r"\b\w+\b", question.lower())) if len(w) > 2]
                                kw_list = generate_query_keywords_cn(question)[:4]  # 使用更多关键词
                                for kw in kw_list:
                                    if not kw:
                                        continue
                                    sub_res = await search_graph(
                                        connector=connector,
                                        q=str(kw),
                                        group_id=group_id,
                                        limit=max(3, search_limit // 2),
                                    )
                                    if isinstance(sub_res, dict):
                                        kw_dialogs.extend(sub_res.get("dialogues", []) or [])
                                        kw_statements.extend(sub_res.get("statements", []) or [])
                                        kw_entities.extend(sub_res.get("entities", []) or [])
                            except Exception:
                                pass

                            # 选项参与关键词检索
                            try:
                                opt_list = extract_candidate_options(question)[:2]
                                for opt in opt_list:
                                    if not opt:
                                        continue
                                    opt_res = await search_graph(
                                        connector=connector,
                                        q=str(opt),
                                        group_id=group_id,
                                        limit=max(3, search_limit // 2),
                                    )
                                    if isinstance(opt_res, dict):
                                        kw_dialogs.extend(opt_res.get("dialogues", []) or [])
                                        kw_statements.extend(opt_res.get("statements", []) or [])
                                        kw_entities.extend(opt_res.get("entities", []) or [])
                            except Exception:
                                pass
                    except Exception as e:
                        print(f"❌ 关键词检索失败: {e}")

                    # 3) 合并、排序并去重
                    all_dialogs = emb_dialogs + kw_dialogs
                    all_statements = emb_statements + kw_statements
                    all_entities = emb_entities + kw_entities

                    def dedup(items: List[Dict[str, Any]], key_field: str = "uuid") -> List[Dict[str, Any]]:
                        seen = set()
                        out = []
                        for it in items:
                            key = str(it.get(key_field, "")) + str(it.get("content", "") + str(it.get("statement", "")))
                            if key not in seen:
                                out.append(it)
                                seen.add(key)
                        return out

                    # 关键技术实体优先排序
                    def enhanced_score(item: Dict[str, Any]) -> float:
                        score_val = item.get("score", 0.0)
                        base_score = float(score_val) if score_val is not None else 0.0
                        content = str(item.get("content", "") + str(item.get("statement", "")))

                        # 关键技术实体奖励
                        key_entities = []
                        if any(term in question for term in ["GPS", "导航", "系统"]):
                            key_entities.extend(["GPS", "导航", "系统", "功能"])
                        if any(term in question for term in ["工作坊", "研讨会", "活动"]):
                            key_entities.extend(["工作坊", "研讨会", "参加"])

                        key_bonus = 0
                        for key_ent in key_entities:
                            if key_ent in content:
                                key_bonus += 1.0

                        # 时间实体奖励
                        time_bonus = 0
                        if is_temporal:
                            time_entities = extract_time_entities(content)
                            time_bonus = len(time_entities) * 0.5

                        return base_score + key_bonus + time_bonus

                    dialogs = dedup(sorted(all_dialogs, key=enhanced_score, reverse=True))
                    statements = dedup(sorted(all_statements, key=enhanced_score, reverse=True))
                    entities = dedup(all_entities, key_field="name")

                    # 4) 构建上下文
                    for d in dialogs:
                        content = str(d.get("content", "")).strip()
                        if content:
                            contexts_all.append(content)
                    for s in statements:
                        stmt_text = str(s.get("statement", "")).strip()
                        if stmt_text:
                            contexts_all.append(stmt_text)
                    # 实体摘要
                    try:
                        scored = [e for e in entities if e.get("score") is not None]
                        top_entities = sorted(scored, key=lambda x: x.get("score", 0), reverse=True)[:3] if scored else entities[:3]
                        if top_entities:
                            summary_lines = []
                            for e in top_entities:
                                name = str(e.get("name", "")).strip()
                                etype = str(e.get("entity_type", "")).strip()
                                score = e.get("score")
                                if name:
                                    meta = []
                                    if etype:
                                        meta.append(f"type={etype}")
                                    if isinstance(score, (int, float)):
                                        meta.append(f"score={score:.3f}")
                                    summary_lines.append(f"EntitySummary: {name}{(' [' + '; '.join(meta) + ']') if meta else ''}")
                            if summary_lines:
                                contexts_all.append("\n".join(summary_lines))
                    except Exception:
                        pass

                # 全局回退
                if not contexts_all and search_type in ("embedding", "hybrid"):
                    try:
                        print("🔁 检索为空，回退到关键词检索...")
                        kw_fallback = await search_graph(
                            connector=connector,
                            q=question,
                            group_id=group_id,
                            limit=max(search_limit, 5),
                        )
                        fb_dialogs = kw_fallback.get("dialogues", []) or []
                        fb_statements = kw_fallback.get("statements", []) or []
                        fb_entities = kw_fallback.get("entities", []) or []

                        for d in fb_dialogs:
                            content = str(d.get("content", "")).strip()
                            if content:
                                contexts_all.append(content)
                        for s in fb_statements:
                            stmt_text = str(s.get("statement", "")).strip()
                            if stmt_text:
                                contexts_all.append(stmt_text)
                        if fb_entities:
                            entity_names = [str(e.get("name", "")).strip() for e in fb_entities[:5] if e.get("name")]
                            if entity_names:
                                contexts_all.append(f"EntitySummary: {', '.join(entity_names)}")

                        dialogs = fb_dialogs if fb_dialogs else dialogs
                        statements = fb_statements if fb_statements else statements
                        entities = fb_entities if fb_entities else entities
                        print(f"↩️ 回退到关键词检索: {len(fb_dialogs)} 对话, {len(fb_statements)} 条陈述, {len(fb_entities)} 个实体")
                    except Exception as fe:
                        print(f"❌ 关键词回退失败: {fe}")

                ent_count = len(entities) if isinstance(entities, list) else 0
                print(f"✅ {search_type}检索成功: {len(dialogs)} 对话, {len(statements)} 条陈述, {ent_count} 个实体")
                if is_temporal:
                    print("⏰ 检测为时间推理问题，已启用时间优化检索")

            except Exception as e:
                print(f"❌ {search_type}检索失败: {e}")
                contexts_all = []

            t1 = time.time()
            latencies_search.append((t1 - t0) * 1000)

            # 智能上下文选择
            context_text = ""
            if contexts_all:
                context_text = smart_context_selection(contexts_all, question, max_chars=context_char_budget)
                # 相对时间解析
                try:
                    context_text = _resolve_relative_times_cn_en(context_text, anchor=datetime.now())
                except Exception:
                    pass
                # 诊断信息
                try:
                    cn_diag = generate_query_keywords_cn(question)[:4]  # 显示更多关键词
                    opts = extract_candidate_options(question)[:2]
                    qlw = [w for w in set(re.findall(r'\b\w+\b', question.lower())) if len(w) > 2][:1]
                    diag_tokens: List[str] = []
                    for t in cn_diag + opts + qlw:
                        if t and t not in diag_tokens:
                            diag_tokens.append(t)
                    print(f"🔍 关键词/选项: {', '.join(diag_tokens)}")
                    preview = context_text[:200].replace('\n', ' ')
                    print(f"🔎 上下文预览: {preview}...")
                    key_preview = preview.strip()
                    if key_preview:
                        preview_counter[key_preview] = preview_counter.get(key_preview, 0) + 1
                except Exception:
                    pass
            else:
                print("❌ 没有检索到有效上下文")
                context_text = "No relevant context found."

            # 记录上下文诊断信息
            per_query_context_counts.append(len(contexts_all))
            per_query_context_avg_tokens.append(avg_context_tokens([context_text]))
            per_query_context_chars.append(len(context_text))

            # LLM 推理（增强技术术语提示）
            options = extract_candidate_options(question)
            if len(options) >= 2:
                opt_lines = "\n".join(f"- {o}" for o in options)
                # 技术术语问题的特殊提示
                if any(term in question for term in ["GPS", "系统", "功能", "工作坊", "研讨会"]):
                    system_prompt = (
                        "You are a QA assistant specializing in technical and activity-related questions. "
                        "Pay special attention to technical terms like GPS, systems, functions, workshops, and seminars. "
                        "Return ONLY one string: exactly one option from the provided candidates. If the context is insufficient, respond with 'Unknown'. "
                        "Focus on matching technical details and activity sequences accurately."
                    )
                elif is_temporal:
                    system_prompt = (
                        "You are a QA assistant specializing in temporal reasoning. Analyze the dates and time relationships in the context carefully. "
                        "Return ONLY one string: exactly one option from the provided candidates. If the context is insufficient, respond with 'Unknown'. "
                        "Pay special attention to date sequences and time intervals."
                    )
                else:
                    system_prompt = (
                        "You are a QA assistant. Respond in the same language as the question. Return ONLY one string: exactly one option from the provided candidates. "
                        "If the context is insufficient, respond with 'Unknown'. If the context expresses a synonym or paraphrase of a candidate, return the closest candidate. "
                        "Do not include explanations."
                    )

                messages = [
                    {"role": "system", "content": system_prompt},
                    {
                        "role": "user",
                        "content": (
                            f"Question: {question}\n\nCandidates:\n{opt_lines}\n\nContext:\n{context_text}\n\nReturn EXACTLY one candidate string (or 'Unknown')."
                        ),
                    },
                ]
            else:
                # 技术术语问题的特殊提示
                if any(term in question for term in ["GPS", "系统", "功能", "工作坊", "研讨会"]):
                    system_prompt = (
                        "You are a QA assistant specializing in technical and activity-related questions. "
                        "Pay special attention to technical terms like GPS, systems, functions, workshops, and seminars. "
                        "If the context contains the answer, return a concise answer phrase focusing on technical details. "
                        "If the answer cannot be determined from the context, respond with 'Unknown'. Return ONLY the final answer string, no explanations."
                    )
                elif is_temporal:
                    system_prompt = (
                        "You are a QA assistant specializing in temporal reasoning. Analyze the dates and time relationships in the context carefully. "
                        "If the context contains the answer, return a concise answer phrase focusing on temporal information. "
                        "If the answer cannot be determined from the context, respond with 'Unknown'. Return ONLY the final answer string, no explanations."
                    )
                else:
                    system_prompt = (
                        "You are a QA assistant. Respond in the same language as the question. If the context contains the answer, return a concise answer phrase. "
                        "If the answer cannot be determined from the context, respond with 'Unknown'. Return ONLY the final answer string, no explanations."
                    )

                messages = [
                    {"role": "system", "content": system_prompt},
                    {
                        "role": "user",
                        "content": f"Question: {question}\n\nContext:\n{context_text}\n\nReturn ONLY the answer (or 'Unknown').",
                    },
                ]

            t2 = time.time()
            # 使用异步调用
            resp = await llm_client.chat(messages=messages)
            t3 = time.time()
            latencies_llm.append((t3 - t2) * 1000)

            # 兼容不同的响应格式
            pred_raw = resp.content.strip() if hasattr(resp, 'content') else (resp["choices"][0]["message"]["content"].strip() if isinstance(resp, dict) else "Unknown")

            # 选项题输出规范化
            pred = pred_raw
            if len(options) >= 2 and not pred_raw.lower().startswith("unknown"):
                def _basic_norm(s: str) -> str:
                    s = s.lower().strip()
                    return re.sub(r"[^\w\s]", " ", s)
                def _jaccard(a: str, b: str) -> float:
                    ta = set(t for t in _basic_norm(a).split() if t)
                    tb = set(t for t in _basic_norm(b).split() if t)
                    if not ta and not tb:
                        return 1.0
                    if not ta or not tb:
                        return 0.0
                    return len(ta & tb) / len(ta | tb)
                best = None
                best_score = -1.0
                for o in options:
                    score = _jaccard(pred_raw, o)
                    if score > best_score:
                        best = o
                        best_score = score
                if best is not None and best_score > 0.0:
                    pred = best

            # 指标
            flag = exact_match(pred, reference)
            f1_val = common_f1(str(pred), str(reference))
            j_val = jaccard(str(pred), str(reference))

            type_correct.setdefault(qtype, []).append(flag)
            type_f1.setdefault(qtype, []).append(f1_val)
            type_jacc.setdefault(qtype, []).append(j_val)

            samples.append({
                "question": question,
                "prediction": pred,
                "answer": reference,
                "question_type": qtype,
                "is_temporal": is_temporal,
                "question_id": item.get("question_id"),
                "options": options,
                "context_count": len(contexts_all),
                "context_chars": len(context_text),
                "retrieved_dialogue_count": len(dialogs),
                "retrieved_statement_count": len(statements),
                "metrics": {
                    "exact_match": bool(flag),
                    "f1": f1_val,
                    "jaccard": j_val
                },
                "timing": {
                    "search_ms": (t1 - t0) * 1000,
                    "llm_ms": (t3 - t2) * 1000
                }
            })

            print(f"🤖 LLM 回答: {pred}")
            print(f"✅ 正确答案: {reference}")
            print(f"📈 当前指标 - Exact Match: {flag}, F1: {f1_val:.3f}, Jaccard: {j_val:.3f}")

        # 聚合结果
        type_acc = {t: (sum(v) / max(len(v), 1)) for t, v in type_correct.items()}
        f1_by_type = {t: (sum(v) / max(len(v), 1)) for t, v in type_f1.items()}
        jacc_by_type = {t: (sum(v) / max(len(v), 1)) for t, v in type_jacc.items()}

        result = {
            "dataset": "longmemeval",
            "items": len(items),
            "accuracy_by_type": type_acc,
            "f1_by_type": f1_by_type,
            "jaccard_by_type": jacc_by_type,
            "samples": samples,
            "latency": {
                "search": latency_stats(latencies_search),
                "llm": latency_stats(latencies_llm),
            },
            "context": {
                "avg_tokens": statistics.mean(per_query_context_avg_tokens) if per_query_context_avg_tokens else 0.0,
                "avg_chars": statistics.mean(per_query_context_chars) if per_query_context_chars else 0.0,
                "count_avg": statistics.mean(per_query_context_counts) if per_query_context_counts else 0.0,
            },
            "params": {
                "group_id": group_id,
                "search_limit": search_limit,
                "context_char_budget": context_char_budget,
                "search_type": search_type,
                "llm_id": SELECTED_LLM_ID,
                "embedding_id": SELECTED_EMBEDDING_ID,
                "sample_size": sample_size,
                "start_index": start_index,
            },
            "timestamp": datetime.now().isoformat()
        }

        # 计算汇总指标
        try:
            total_items = max(len(samples), 1)
            correct_count = sum(1 for s in samples if s.get("metrics", {}).get("exact_match"))
            score_accuracy = (correct_count / total_items) * 100.0

            total_latencies_ms = []
            for s in samples:
                t = s.get("timing", {})
                total_latencies_ms.append(float(t.get("search_ms", 0.0)) + float(t.get("llm_ms", 0.0)))
            total_lat_stats = latency_stats(total_latencies_ms) if total_latencies_ms else {"p50": 0.0, "iqr": 0.0}
            latency_median_s = total_lat_stats.get("p50", 0.0) / 1000.0
            latency_iqr_s = total_lat_stats.get("iqr", 0.0) / 1000.0

            avg_ctx_tokens = statistics.mean(per_query_context_avg_tokens) if per_query_context_avg_tokens else 0.0
            avg_ctx_tokens_k = avg_ctx_tokens / 1000.0

            result["metric_summary"] = {
                "score_accuracy": score_accuracy,
                "latency_median_s": latency_median_s,
                "latency_iqr_s": latency_iqr_s,
                "avg_context_tokens_k": avg_ctx_tokens_k,
            }
        except Exception:
            result["metric_summary"] = {
                "score_accuracy": 0.0,
                "latency_median_s": 0.0,
                "latency_iqr_s": 0.0,
                "avg_context_tokens_k": 0.0,
            }

        # 诊断信息
        try:
            dups = sorted([(k, c) for k, c in preview_counter.items() if c > 1], key=lambda x: -x[1])[:5]
            result["diagnostics"] = {
                "duplicate_previews_top": [{"count": c, "preview": k[:120]} for k, c in dups],
                "unique_preview_count": len(preview_counter),
            }
        except Exception:
            pass

        return result

    finally:
        await connector.close()


def main():
    load_dotenv()
    parser = argparse.ArgumentParser(description="LongMemEval 评估测试脚本（增强技术术语检索版）")
    parser.add_argument("--sample-size", type=int, default=3, help="样本数量（<=0 表示全部）")
    parser.add_argument("--all", action="store_true", help="评估全部样本（覆盖 --sample-size）")
    parser.add_argument("--start-index", type=int, default=0, help="起始样本索引")
    parser.add_argument("--group-id", type=str, default="longmemeval_zh_bak_3", help="图数据库 Group ID")
    parser.add_argument("--search-limit", type=int, default=8, help="检索条数上限")
    parser.add_argument("--context-char-budget", type=int, default=4000, help="上下文字符预算")
    parser.add_argument("--llm-temperature", type=float, default=0.0, help="LLM 温度")
    parser.add_argument("--llm-max-tokens", type=int, default=16, help="LLM 最大输出 token")
    parser.add_argument("--search-type", type=str, default="hybrid", choices=["embedding","keyword","hybrid"], help="检索类型")
    parser.add_argument("--data-path", type=str, default=None, help="数据集路径")
    args = parser.parse_args()

    sample_size = 0 if args.all else args.sample_size

    result = asyncio.run(
        run_longmemeval_test(
            sample_size=sample_size,
            group_id=args.group_id,
            search_limit=args.search_limit,
            context_char_budget=args.context_char_budget,
            llm_temperature=args.llm_temperature,
            llm_max_tokens=args.llm_max_tokens,
            search_type=args.search_type,
            data_path=args.data_path,
            start_index=args.start_index,
        )
    )

    # 打印结果
    print("\n" + "="*50)
    print("📊 LongMemEval 测试结果:")
    print(f"   样本数量: {result['items']}")

    if result['accuracy_by_type']:
        print("\n📈 按问题类型细分:")
        for qtype, acc in result['accuracy_by_type'].items():
            print(f"   {qtype}:")
            print(f"     Score (Accuracy): {acc:.3f}")

    print(f"\n📊 指标总览:")
    ms = result.get('metric_summary', {})
    print(f"   Score (Accuracy): {ms.get('score_accuracy', 0.0):.1f}%")
    print(f"   Latency (s): median {ms.get('latency_median_s', 0.0):.3f}s")
    print(f"   Latency IQR (s): {ms.get('latency_iqr_s', 0.0):.3f}s")
    print(f"   Avg Context Tokens (k): {ms.get('avg_context_tokens_k', 0.0):.3f}k")

    print(f"\n⏱️  细分性能指标:")
    print(f"   检索延迟(均值): {result['latency']['search']['mean']:.1f}ms")
    print(f"   LLM延迟(均值): {result['latency']['llm']['mean']:.1f}ms")
    print(f"   上下文长度(均值): {result['context']['avg_chars']:.0f} 字符")


    # 保存结果到文件
    try:
        out_dir = os.path.join(PROJECT_ROOT, "evaluation", "longmemeval", "results")
        os.makedirs(out_dir, exist_ok=True)
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        out_path = os.path.join(out_dir, f"longmemeval_{result['params']['search_type']}_{ts}.json")
        with open(out_path, "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
        print(f"\n💾 结果已保存: {out_path}")
    except Exception as e:
        print(f"⚠️ 结果保存失败: {e}")


if __name__ == "__main__":
    main()
