import json
from datetime import datetime, timezone
from typing import Dict, List, Optional, Tuple, Union
from neo4j import GraphDatabase


class MilitaryKnowledgeGraph:
    def __init__(self, uri: str, auth: Tuple[str, str], Ontology: List[str] = None):
        """
        初始化知识图谱
        
        Args:
            uri: Neo4j 数据库连接地址
            auth: 认证信息 (用户名, 密码)
            Ontology: 节点类型列表，用于创建唯一性约束和类型校验
        """
        self.driver = GraphDatabase.driver(uri, auth=auth)
        
        # 默认节点类型
        self.Ontology = Ontology or ["舰艇", "系统", "子系统", "部件", "一级零部件", "二级零部件", "故障模式", "维修方案", "操作项目", "备品备件", "维修手册", "操作使用手册"]
        
        self._setup_constraints()

    def close(self):
        """关闭数据库连接"""
        self.driver.close()

    def _setup_constraints(self):
        """为每个节点类型设置 名称 字段的唯一性约束"""
        with self.driver.session() as session:
            for node_type in self.Ontology:
                try:
                    session.run(f"CREATE CONSTRAINT IF NOT EXISTS FOR (n:{node_type}) REQUIRE n.名称 IS UNIQUE")
                except Exception as e:
                    print(f"创建约束失败 {node_type}: {e}")

    def construct_fact(self, from_name: str, to_name: str, rel_type: str, attributes: Dict) -> str:
        """构造关系的自然语言描述（fact）"""
        attr_str = ", ".join([f"{k}：{v}" for k, v in attributes.items() if v])
        if attr_str:
            return f"{from_name} {rel_type} {to_name}，{attr_str}"
        else:
            return f"{from_name} {rel_type} {to_name}"

    # def _resolve_attribute_conflicts(self, existing_attrs: Dict, new_attrs: Dict) -> Dict:
    #     """解决属性冲突（保留最新状态、最早开始时间、最晚结束时间等）"""
    #     resolved = existing_attrs.copy()
    #     current_time = datetime.now(timezone.utc).isoformat()

    #     for key, new_value in new_attrs.items():
    #         if new_value is None or new_value == "":
    #             continue

    #         if key not in existing_attrs:
    #             resolved[key] = new_value
    #         elif existing_attrs[key] != new_value:
    #             if key in ["当前状态", "执行状态", "完成状态"]:
    #                 resolved[key] = new_value
    #             elif key in ["开始时间", "下达时间", "承担时间"]:
    #                 if new_value < existing_attrs[key]:
    #                     resolved[key] = new_value
    #             elif key in ["结束时间", "执行时限"]:
    #                 if new_value > existing_attrs[key]:
    #                     resolved[key] = new_value
    #             else:
    #                 resolved[key] = new_value
    #                 if f"{key}_history" not in resolved:
    #                     resolved[f"{key}_history"] = []
    #                 resolved[f"{key}_history"].append({
    #                     "previous_value": existing_attrs[key],
    #                     "new_value": new_value,
    #                     "update_time": current_time
    #                 })

    #     return resolved
    def _resolve_attribute_conflicts(self, existing_attrs: Dict, new_attrs: Dict) -> Dict:
        """
        解决属性冲突：
        1. 状态类：直接覆盖
        2. 开始时间类：取最早时间
        3. 结束时间类：取最晚时间
        4. 其他类：直接覆盖 (不再记录历史)
        """
        resolved = existing_attrs.copy()
        # current_time 在此版本中不再需要，但保留导入以防后续扩展使用
        # current_time = datetime.now(timezone.utc).isoformat()

        for key, new_value in new_attrs.items():
            if new_value is None or new_value == "":
                continue

            if key not in existing_attrs:
                # 新属性直接添加
                resolved[key] = new_value
            elif existing_attrs[key] != new_value:
                # 属性存在且值不同，根据类型处理
                if key in ["当前状态", "执行状态", "完成状态"]:
                    # 状态类：直接覆盖为新值
                    resolved[key] = new_value
                
                elif key in ["开始时间", "下达时间", "承担时间"]:
                    # 开始时间类：保留更早的时间
                    if new_value < existing_attrs[key]:
                        resolved[key] = new_value
                
                elif key in ["结束时间", "执行时限"]:
                    # 结束时间类：保留更晚的时间
                    if new_value > existing_attrs[key]:
                        resolved[key] = new_value
                
                else:
                    # 其他普通属性：直接覆盖，不再生成 _history 字段
                    resolved[key] = new_value

        return resolved
    def _create_relationship_with_timeline(self, tx, from_name: str, to_name: str,
                                           rel_type: str, attributes: Dict) -> bool:
        """创建或更新带有时间线的关系"""
        current_time = datetime.now(timezone.utc).isoformat()
        clean_attributes = {k: v for k, v in attributes.items() if v is not None and v != ""}

        fact = self.construct_fact(from_name, to_name, rel_type, attributes)
        clean_attributes["fact"] = fact

        existing_rel = tx.run("""
        MATCH (a {名称: $from_name})-[r]->(b {名称: $to_name})
        WHERE type(r) = $rel_type
        RETURN r, properties(r) as props
        """, from_name=from_name, to_name=to_name, rel_type=rel_type).single()

        if existing_rel:
            existing_props = existing_rel["props"]
            updated_props = existing_props.copy()

            for key, new_value in clean_attributes.items():
                if key == "fact":
                    continue
                if key not in existing_props or existing_props[key] != new_value:
                    timeline_key = f"{key}_timeline"
                    if timeline_key not in updated_props:
                        updated_props[timeline_key] = []

                    timeline_entry_dict = {
                        "value": new_value,
                        "timestamp": current_time,
                        "source": "text_extraction"
                    }
                    if key in existing_props:
                        timeline_entry_dict["previous_value"] = existing_props[key]

                    timeline_entry_str = json.dumps(timeline_entry_dict, ensure_ascii=False)
                    updated_props[timeline_key].append(timeline_entry_str)
                    updated_props[key] = new_value

            if "fact" in clean_attributes:
                fact_timeline_key = "fact_timeline"
                if fact_timeline_key not in updated_props:
                    updated_props[fact_timeline_key] = []

                fact_entry_str = json.dumps({
                    "value": clean_attributes["fact"],
                    "timestamp": current_time,
                    "source": "text_extraction"
                }, ensure_ascii=False)
                updated_props[fact_timeline_key].append(fact_entry_str)
                updated_props["fact"] = clean_attributes["fact"]

            updated_props["last_updated"] = current_time

            query = f"""
            MATCH (a {{名称: $from_name}})-[r:{rel_type}]->(b {{名称: $to_name}})
            SET r = $properties
            RETURN r
            """
            tx.run(query, from_name=from_name, to_name=to_name, properties=updated_props)
            print(f"更新关系: {from_name} -[{rel_type}]-> {to_name}")

        else:
            relationship_props = clean_attributes.copy()

            for key, value in clean_attributes.items():
                timeline_key = f"{key}_timeline"
                timeline_entry_dict = {
                    "value": value,
                    "timestamp": current_time,
                    "source": "text_extraction"
                }
                timeline_entry_str = json.dumps(timeline_entry_dict, ensure_ascii=False)
                relationship_props[timeline_key] = [timeline_entry_str]

            relationship_props["created_at"] = current_time
            relationship_props["last_updated"] = current_time
            relationship_props["update_count"] = 1

            query = f"""
            MATCH (a {{名称: $from_name}}), (b {{名称: $to_name}})
            CREATE (a)-[r:{rel_type} $properties]->(b)
            RETURN r
            """
            tx.run(query, from_name=from_name, to_name=to_name, properties=relationship_props)
            print(f"创建关系: {from_name} -[{rel_type}]-> {to_name}")

        return True

    def _create_or_update_node(self, tx, node_type: str, 名称: str, attributes: Dict) -> bool:
        """
        创建或更新节点，支持 knowledge_source 等复杂字段
        
        Args:
            tx: Neo4j 事务对象
            node_type: 节点类型
            名称: 节点名称（唯一标识）
            attributes: 节点属性字典
        """
        if not 名称 or not node_type:
            return False

        # 安全校验：只允许预定义的节点类型
        if node_type not in self.Ontology:
            print(f"跳过未知节点类型: {node_type}")
            return False

        # 预处理 attributes：将 knowledge_source 转为 JSON 字符串
        clean_attributes = {"名称": 名称}  # 确保名称字段存在
        for k, v in attributes.items():
            if v is None or v == "":
                continue
            if k == "knowledge_source" and isinstance(v, list):
                clean_attributes[k] = json.dumps(v, ensure_ascii=False)
            else:
                clean_attributes[k] = v

        existing_node = tx.run(
            f"MATCH (n:{node_type} {{名称: $名称}}) RETURN n, properties(n) as props",
            名称=名称
        ).single()

        current_time = datetime.now(timezone.utc).isoformat()

        if existing_node:
            existing_props = existing_node["props"]
            existing_attrs = {k: v for k, v in existing_props.items()
                             if not k.endswith('_timeline') and k not in ['created_at', 'last_updated']}

            resolved_attributes = self._resolve_attribute_conflicts(existing_attrs, clean_attributes)

            timeline_data = {k: v for k, v in existing_props.items()
                            if k.endswith('_timeline') or k in ['created_at', 'last_updated']}

            final_attributes = {**timeline_data, **resolved_attributes}
            final_attributes["last_updated"] = current_time

            query = f"""
            MATCH (n:{node_type} {{名称: $名称}})
            SET n = $attributes
            RETURN n
            """
            tx.run(query, 名称=名称, attributes=final_attributes)
            print(f"更新节点: {node_type} - {名称}")
        else:
            clean_attributes["created_at"] = current_time
            clean_attributes["last_updated"] = current_time

            query = f"""
            CREATE (n:{node_type} $attributes)
            RETURN n
            """
            tx.run(query, attributes=clean_attributes)
            print(f"创建节点: {node_type} - {名称}")

        return True

    def process_data(self, data: Dict) -> Tuple[int, int]:
        """
        处理字典数据并写入Neo4j
        
        Args:
            data: 包含 entities 和 relationships 的字典
            
        Returns:
            Tuple[int, int]: (实体数量, 关系数量)
        """
        if not data or not isinstance(data, dict):
            print("无效的数据格式，需要字典类型")
            return 0, 0

        entity_count = 0
        relationship_count = 0

        with self.driver.session() as session:
            # 创建/更新实体节点
            for entity in data.get("entities", []):
                try:
                    # 提取 properties，并注入 knowledge_source（如果存在）
                    props = entity.get("properties", {}).copy()
                    if "knowledge_source" in entity:
                        props["knowledge_source"] = entity["knowledge_source"]

                    # 从 properties 中提取名称
                    名称 = props.pop("名称", None)
                    if not 名称:
                        print(f"跳过无名称节点: {entity}")
                        continue

                    success = session.execute_write(
                        self._create_or_update_node,
                        entity["type"],
                        名称,
                        props
                    )
                    if success:
                        entity_count += 1
                except Exception as e:
                    print(f"处理节点失败: {entity}, 错误: {e}")

            # 创建/更新关系
            for relationship in data.get("relationships", []):
                try:
                    success = session.execute_write(
                        self._create_relationship_with_timeline,
                        relationship["from_entity"],
                        relationship["to_entity"],
                        relationship["type"],
                        relationship.get("properties", {})
                    )
                    if success:
                        relationship_count += 1
                except Exception as e:
                    print(f"处理关系失败: {relationship}, 错误: {e}")

            print(f"成功处理 {entity_count} 个节点和 {relationship_count} 个关系")
            return entity_count, relationship_count

    def get_relationship_history(self, from_name: str, to_name: str, rel_type: str) -> Optional[Dict]:
        """获取关系的完整历史记录"""
        with self.driver.session() as session:
            result = session.run("""
            MATCH (a {名称: $from_name})-[r]->(b {名称: $to_name})
            WHERE type(r) = $rel_type
            RETURN properties(r) as props
            """, from_name=from_name, to_name=to_name, rel_type=rel_type)

            record = result.single()
            return record["props"] if record else None

    def get_property_timeline(self, from_name: str, to_name: str, rel_type: str,
                              property_name: str) -> Optional[List]:
        """获取特定属性的时间线（返回字符串列表，需 json.loads 解析）"""
        history = self.get_relationship_history(from_name, to_name, rel_type)
        if history:
            return history.get(f"{property_name}_timeline", [])
        return None

    def clear_database(self):
        """清空数据库（谨慎使用）"""
        with self.driver.session() as session:
            session.run("MATCH (n) DETACH DELETE n")
            print("数据库已清空")

# if __name__ == "__main__":
#     # 连接本地 Neo4j（请确保服务已启动）
#     try:
#         URI = "bolt://192.168.0.46:57687"
#         USERNAME = "neo4j"
#         PASSWORD = "zdht123@"
#         with open("/storage01/home/hdf/project/wxkgrag/kgextract/output/output3/neo4j_export_merged1.json", "r", encoding="utf-8") as f:
#             data = json.load(f)
#         kg = MilitaryKnowledgeGraph(
#                 uri="bolt://192.168.0.46:57687",
#                 auth=("neo4j", "zdht123@"),
#             )
#         entity_count, rel_count = kg.process_data(data)


#     finally:
#         # kg.clear_database()  # 取消注释以清理测试数据
#         kg.close()

