#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
关联匹配标签查找工具

从how.json中提取低相似度匹配结果，通过dimension_associations_analysis.json查找关联，
最终在optimized_clustered_data中找到匹配的标签特征。
"""

import json
import logging
import copy
from typing import Dict, List, Any, Optional, Tuple, Set
import argparse


# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)


class AssociatedTagsFinder:
    """关联标签查找器"""

    def __init__(
        self,
        how_json_path: str,
        dimension_associations_path: str,
        optimized_clustered_data_path: str
    ):
        """
        初始化查找器

        Args:
            how_json_path: how.json文件路径
            dimension_associations_path: dimension_associations_analysis.json文件路径
            optimized_clustered_data_path: optimized_clustered_data文件路径
        """
        self.how_json_path = how_json_path
        self.dimension_associations_path = dimension_associations_path
        self.optimized_clustered_data_path = optimized_clustered_data_path

        # 加载数据
        self.how_data = self._load_json(how_json_path)
        self.dimension_associations = self._load_json(dimension_associations_path)
        self.optimized_clustered_data = self._load_json(optimized_clustered_data_path)

        logger.info(f"成功加载三个JSON文件")

    def _load_json(self, file_path: str) -> Dict:
        """加载JSON文件"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except Exception as e:
            logger.error(f"加载文件 {file_path} 失败: {e}")
            raise

    def build_classification_path(self, feature_classification: List[str]) -> Tuple[str, str]:
        """
        构建特征分类路径

        Args:
            feature_classification: 特征分类数组，如 ["实物道具", "场景与道具实质", "实质"]

        Returns:
            (完整路径, leaf_category)
            如: ("实质/场景与道具/实物道具", "实物道具")
        """
        if not feature_classification:
            return "", ""

        # 步骤1: 去掉中间元素的"实质"后缀（保留第一个和最后一个）
        cleaned = []
        for i, item in enumerate(feature_classification):
            if i == len(feature_classification) - 1:  # 最后一个保留
                cleaned.append(item)
            elif item.endswith("实质") and i != 0:  # 中间的去掉"实质"后缀
                cleaned.append(item[:-2])  # 去掉"实质"两个字
            else:
                cleaned.append(item)

        # 步骤2: 反转数组
        reversed_list = list(reversed(cleaned))

        # 步骤3: 拼接路径
        path = "/".join(reversed_list)

        # leaf_category是原始数组的第一个元素
        leaf_category = feature_classification[0]

        logger.debug(f"特征分类: {feature_classification}")
        logger.debug(f"清理后: {cleaned}")
        logger.debug(f"反转后: {reversed_list}")
        logger.debug(f"路径: {path}, leaf: {leaf_category}")

        return path, leaf_category

    def extract_matching_results(self) -> List[Dict[str, Any]]:
        """
        从how.json提取匹配结果

        Returns:
            匹配结果列表，每个元素包含需要进一步处理的特征信息
        """
        results = []
        how_result = self.how_data.get('how解构结果', {})

        # 遍历三个维度：灵感点列表、关键点列表、目的点列表
        for level_name, level_list in how_result.items():
            if not isinstance(level_list, list):
                continue

            logger.info(f"处理 {level_name}，共 {len(level_list)} 项")

            for item_idx, item in enumerate(level_list):
                item_name = item.get('名称', f'未命名-{item_idx}')
                how_steps = item.get('how步骤列表', [])

                for step_idx, step in enumerate(how_steps):
                    features = step.get('特征列表', [])

                    for feature_idx, feature in enumerate(features):
                        feature_name = feature.get('特征名称', '')
                        feature_weight = feature.get('权重', 0)
                        match_results = feature.get('匹配结果', [])

                        if not match_results:
                            logger.debug(f"  {level_name}/{item_name}/{feature_name}: 无匹配结果")
                            continue

                        # 检查是否所有相似度都 < 0.8
                        all_similarities = []
                        for match in match_results:
                            similarity = match.get('匹配结果', {}).get('相似度', 0)
                            all_similarities.append(similarity)

                        max_similarity = max(all_similarities) if all_similarities else 0

                        # 只有所有相似度都 < 0.8 才处理
                        if max_similarity >= 0.8:
                            logger.debug(f"  {level_name}/{item_name}/{feature_name}: "
                                       f"最高相似度 {max_similarity:.3f} >= 0.8，跳过")
                            continue

                        # 找到得分最高的匹配
                        best_match = max(match_results,
                                       key=lambda x: x.get('匹配结果', {}).get('相似度', 0))

                        best_similarity = best_match.get('匹配结果', {}).get('相似度', 0)

                        logger.info(f"  {level_name}/{item_name}/{feature_name}: "
                                  f"最高匹配 {best_match.get('人设特征名称')} "
                                  f"(相似度 {best_similarity:.3f})")

                        results.append({
                            '原始特征名称': feature_name,
                            '来源层级': level_name,
                            '权重': feature_weight,
                            '所属点名称': item_name,
                            '最高匹配信息': {
                                '人设特征名称': best_match.get('人设特征名称'),
                                '人设特征层级': best_match.get('人设特征层级'),
                                '特征类型': best_match.get('特征类型'),
                                '特征分类': best_match.get('特征分类', []),
                                '相似度': best_similarity,
                                '匹配说明': best_match.get('匹配结果', {}).get('说明', '')
                            }
                        })

        logger.info(f"共提取 {len(results)} 个需要处理的匹配结果")
        return results

    def find_associations(
        self,
        classification_path: str,
        source_level: str
    ) -> List[Dict[str, Any]]:
        """
        从dimension_associations中查找关联节点

        Args:
            classification_path: 特征分类路径，如 "实质/场景与道具/实物道具"
            source_level: 源层级，如 "灵感点"、"关键点"、"目的点"

        Returns:
            关联节点列表
        """
        associations = []

        # 确定维度名称
        if '灵感点' in source_level:
            dimension_key = '灵感点维度'
        elif '关键点' in source_level:
            dimension_key = '关键点维度'
        elif '目的点' in source_level:
            dimension_key = '目的点维度'
        else:
            logger.warning(f"未知的源层级: {source_level}")
            return associations

        # 获取该维度下的数据
        single_dim_analysis = self.dimension_associations.get('单维度关联分析', {})
        dimension_data = single_dim_analysis.get(dimension_key, {})

        if not dimension_data:
            logger.warning(f"未找到维度数据: {dimension_key}")
            return associations

        logger.info(f"在 {dimension_key} 中查找路径: {classification_path}")

        # 遍历所有方向（如"关键点→目的点"、"关键点→灵感点"）
        for direction_key, direction_data in dimension_data.items():
            if direction_key == '说明':
                continue

            # 查找源分类
            if classification_path in direction_data:
                source_data = direction_data[classification_path]
                logger.info(f"  在 {direction_key} 中找到源分类")

                # 获取关联节点（可能是"与目的点的关联"、"与关键点的关联"等）
                for assoc_key in source_data.keys():
                    if assoc_key.startswith('与') and assoc_key.endswith('的关联'):
                        assoc_list = source_data[assoc_key]
                        logger.info(f"    找到 {assoc_key}: {len(assoc_list)} 个关联")

                        for assoc_item in assoc_list:
                            associations.append({
                                '来源方向': direction_key,
                                '关联类型': assoc_key,
                                '目标分类': assoc_item.get('目标分类'),
                                '目标层级': assoc_item.get('目标层级'),
                                '共同帖子数': assoc_item.get('共同帖子数'),
                                'Jaccard相似度': assoc_item.get('Jaccard相似度'),
                                '共同帖子ID': assoc_item.get('共同帖子ID', [])
                            })

        logger.info(f"共找到 {len(associations)} 个关联节点")
        return associations

    def find_features_by_path(self, target_classification: str) -> List[Dict[str, Any]]:
        """
        从optimized_clustered_data中根据路径查找特征列表

        Args:
            target_classification: 目标分类路径，如 "意图/粉丝互动维系意图/互动关系维护/粉丝维系"

        Returns:
            特征列表
        """
        if not target_classification:
            return []

        # 分割路径
        path_parts = target_classification.split('/')
        logger.info(f"查找路径: {target_classification}")
        logger.debug(f"  路径层级: {path_parts}")

        # 确定顶层key
        first_part = path_parts[0]

        # 映射到optimized_clustered_data的顶层key
        top_level_map = {
            '意图': '目的点',
            '要素': '目的点',
            '实质': None,  # 实质可能在灵感点列表或关键点列表
            '形式': None,
            '场景': None
        }

        # 尝试在不同的顶层中查找
        top_level_keys_to_try = []
        if first_part in top_level_map:
            mapped = top_level_map[first_part]
            if mapped:
                top_level_keys_to_try.append(mapped)

        # 如果没有映射，尝试所有顶层
        if not top_level_keys_to_try:
            top_level_keys_to_try = ['灵感点列表', '关键点列表', '目的点']

        # 在每个顶层中尝试查找
        for top_key in top_level_keys_to_try:
            current = self.optimized_clustered_data.get(top_key)
            if not current:
                continue

            logger.debug(f"  尝试在 {top_key} 中查找")

            # 逐层导航
            found = True
            for i, part in enumerate(path_parts):
                if isinstance(current, dict):
                    if part in current:
                        current = current[part]
                        logger.debug(f"    找到第 {i+1} 层: {part}")
                    else:
                        found = False
                        logger.debug(f"    未找到第 {i+1} 层: {part}")
                        break
                else:
                    found = False
                    logger.debug(f"    第 {i} 层不是字典类型")
                    break

            if found and isinstance(current, dict):
                features = current.get('特征列表', [])
                if features:
                    logger.info(f"  在 {top_key} 中找到 {len(features)} 个特征")
                    # 返回深拷贝，避免多个结果共享同一特征对象
                    return copy.deepcopy(features)

        logger.warning(f"  未找到路径对应的特征列表: {target_classification}")
        return []

    # 注释掉：不再需要匹配特征名称，直接输出所有找到的特征
    # def match_features(
    #     self,
    #     original_feature_name: str,
    #     found_features: List[Dict[str, Any]]
    # ) -> List[Dict[str, Any]]:
    #     """
    #     匹配特征名称
    #
    #     Args:
    #         original_feature_name: 原始特征名称
    #         found_features: 查找到的特征列表
    #
    #     Returns:
    #         匹配到的特征列表
    #     """
    #     matched = []
    #     for feature in found_features:
    #         feature_name = feature.get('特征名称', '')
    #         if feature_name == original_feature_name:
    #             logger.info(f"    匹配成功: {feature_name}")
    #             matched.append(feature)
    #
    #     return matched

    def process(self) -> List[Dict[str, Any]]:
        """
        执行完整的处理流程

        Returns:
            最终结果列表
        """
        logger.info("=" * 60)
        logger.info("开始处理")
        logger.info("=" * 60)

        # 步骤1: 提取匹配结果
        logger.info("\n步骤1: 从how.json提取匹配结果")
        matching_results = self.extract_matching_results()

        # 步骤2-9: 处理每个匹配结果
        final_results = []

        for idx, result in enumerate(matching_results, 1):
            logger.info(f"\n处理第 {idx}/{len(matching_results)} 个结果")
            logger.info(f"  原始特征: {result['原始特征名称']}")
            logger.info(f"  来源层级: {result['来源层级']}")

            # 构建分类路径
            feature_classification = result['最高匹配信息']['特征分类']
            classification_path, leaf_category = self.build_classification_path(feature_classification)

            result['关联分类路径'] = classification_path
            result['leaf_category'] = leaf_category

            logger.info(f"  分类路径: {classification_path}")

            # 查找关联
            source_level = result['最高匹配信息']['人设特征层级']
            associations = self.find_associations(classification_path, source_level)

            # 对每个关联查找特征
            result['找到的关联'] = []

            for assoc in associations:
                target_classification = assoc['目标分类']
                logger.info(f"  处理关联: {target_classification}")

                # 查找特征
                found_features = self.find_features_by_path(target_classification)

                # 直接输出找到的所有特征，不再做名称匹配
                if found_features:
                    result['找到的关联'].append({
                        '来源方向': assoc['来源方向'],
                        '关联类型': assoc['关联类型'],
                        '目标分类路径': target_classification,
                        '共同帖子数': assoc['共同帖子数'],
                        'Jaccard相似度': assoc['Jaccard相似度'],
                        '特征列表': found_features
                    })

            # 为特征添加 search_word 字段（组合词去重）
            self._add_search_words(result)

            final_results.append(result)

        logger.info(f"\n" + "=" * 60)
        logger.info(f"处理完成，共生成 {len(final_results)} 个结果")
        logger.info("=" * 60)

        return final_results

    def _add_search_words(self, result: Dict[str, Any]) -> Dict[str, int]:
        """
        为结果项中的所有特征添加 search_word 字段

        在同一结果项内去重，首次出现填充组合词，重复的设为 null

        Args:
            result: 单个结果项

        Returns:
            统计信息字典：{'唯一组合词数': int, '重复词数': int}
        """
        # 获取基础词（人设特征名称）
        base_word = result.get('最高匹配信息', {}).get('人设特征名称', '')

        if not base_word:
            logger.debug(f"  未找到人设特征名称，跳过生成检索词")
            return {'唯一组合词数': 0, '重复词数': 0}

        # 用于去重的集合（在当前结果项范围内）
        seen_words: Set[str] = set()
        unique_count = 0
        duplicate_count = 0

        # 遍历所有关联的特征列表
        for assoc in result.get('找到的关联', []):
            for feature in assoc.get('特征列表', []):
                feature_name = feature.get('特征名称', '')

                if not feature_name:
                    feature['search_word'] = None
                    continue

                # 生成组合词
                search_word = f"{base_word} {feature_name}"

                # 检查是否重复
                if search_word not in seen_words:
                    # 首次出现，填充
                    feature['search_word'] = search_word
                    seen_words.add(search_word)
                    unique_count += 1
                else:
                    # 重复，留空
                    feature['search_word'] = None
                    duplicate_count += 1

        logger.debug(f"  生成检索词: 唯一 {unique_count} 个，重复 {duplicate_count} 个")

        return {
            '唯一组合词数': unique_count,
            '重复词数': duplicate_count
        }

    def save_results(self, results: List[Dict[str, Any]], output_path: str):
        """保存结果到JSON文件"""
        try:
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(results, f, ensure_ascii=False, indent=2)
            logger.info(f"结果已保存到: {output_path}")
        except Exception as e:
            logger.error(f"保存结果失败: {e}")
            raise


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='关联匹配标签查找工具')
    parser.add_argument(
        '--how-json',
        default='690d977d0000000007036331_how.json',
        help='how.json文件路径（默认: 690d977d0000000007036331_how.json）'
    )
    parser.add_argument(
        '--dimension-associations',
        default='dimension_associations_analysis.json',
        help='dimension_associations文件路径（默认: dimension_associations_analysis.json）'
    )
    parser.add_argument(
        '--optimized-clustered',
        default='optimized_clustered_data_gemini-3-pro-preview.json',
        help='optimized_clustered_data文件路径（默认: optimized_clustered_data_gemini-3-pro-preview.json）'
    )
    parser.add_argument(
        '--output',
        default='associated_tags_results_with_search.json',
        help='输出文件路径（默认: associated_tags_results_with_search.json）'
    )
    parser.add_argument(
        '--debug',
        action='store_true',
        help='启用调试日志'
    )

    args = parser.parse_args()

    # 设置日志级别
    if args.debug:
        logger.setLevel(logging.DEBUG)

    # 创建查找器
    finder = AssociatedTagsFinder(
        how_json_path=args.how_json,
        dimension_associations_path=args.dimension_associations,
        optimized_clustered_data_path=args.optimized_clustered
    )

    # 执行处理
    results = finder.process()

    # 保存结果
    finder.save_results(results, args.output)

    # 输出统计信息
    total_associations = sum(len(r['找到的关联']) for r in results)
    total_features = sum(
        sum(len(a['特征列表']) for a in r['找到的关联'])
        for r in results
    )

    # 统计 search_word
    unique_search_words = 0
    duplicate_search_words = 0
    for r in results:
        for assoc in r.get('找到的关联', []):
            for feature in assoc.get('特征列表', []):
                sw = feature.get('search_word')
                if sw is None:
                    duplicate_search_words += 1
                elif sw:  # 非空字符串
                    unique_search_words += 1

    logger.info("\n统计信息:")
    logger.info(f"  处理的结果项数: {len(results)}")
    logger.info(f"  找到的关联数: {total_associations}")
    logger.info(f"  关联中的特征总数: {total_features}")
    logger.info(f"  唯一组合词数: {unique_search_words}")
    logger.info(f"  重复过滤的词数: {duplicate_search_words}")
    if total_features > 0:
        logger.info(f"  去重率: {duplicate_search_words / total_features * 100:.1f}%")


if __name__ == '__main__':
    main()
