from openai import OpenAI
from typing import List, Dict
import json
from langchain_text_splitters import MarkdownHeaderTextSplitter, CharacterTextSplitter
import requests
import json
from modelsAPI.model_api import OpenaiAPI

def _split_markdown(text, chunk_size=2048, chunk_overlap=50):
    """
    根据 max_level 动态生成 headers_to_split_on 配置
    规则：
    - max_level=1 → 只按 # 分割
    - max_level=2 → 按 # 和 ## 分割
    - 以此类推
    """
    # 动态生成 headers_to_split_on 配置（目前固定为 Header_1）
    headers_to_split_on = [("#", "Header_1")]

    # 初始化分割器
    splitter = MarkdownHeaderTextSplitter(
        headers_to_split_on=headers_to_split_on,
        strip_headers=True
    )
    chunks = splitter.split_text(text)

    char_splitter = CharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separator="\n\n"  # 更合理的分隔符
    )

    final_chunks = []
    start_chars = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "第", "十",
                   "一", "二", "三", "四", "五", "六", "七", "八", "九"," 故障"]
    new_header = []

    # 提取所有 Header_1 并筛选出“有效标题”
    for chunk in chunks:
        header_1 = chunk.metadata.get('Header_1', '')
        if header_1 and header_1[0] in start_chars:
            new_header.append(header_1)

    new_header_set = set(new_header)

    # 构建带标题前缀的 final_chunks
    for chunk in chunks:
        header_1 = chunk.metadata.get('Header_1', '')
        content = chunk.page_content
        if len(content) <= 20000:
            chunk_with_header = f"{header_1}\n{content}" if header_1 else content
            final_chunks.append(chunk_with_header)
        else:
            split_texts = char_splitter.split_documents([chunk])
            for split_chunk in split_texts:
                chunk_with_header = f"{header_1}\n{split_chunk.page_content}" if header_1 else split_chunk.page_content
                final_chunks.append(chunk_with_header)

    # ===== 重新分组，并记录每个组的 header =====
    rechunked = []
    rechunked_headers = []  # 新增：每个 rechunked 块对应的 header
    current = ""
    current_header = ""  # 当前正在构建的块所属的标题

    for chunk in final_chunks:
        is_new_section = False
        matched_header = ""

        # 检查是否以某个 new_header 开头
        for h in new_header_set:
            if chunk.startswith(h + "\n"):
                is_new_section = True
                matched_header = h
                break

        if is_new_section:
            # 保存上一段
            if current:
                rechunked.append(current)
                rechunked_headers.append(current_header)
            # 开启新段
            current = chunk
            current_header = matched_header
        else:
            # 合并到当前段
            if current:
                current += "\n" + chunk
            else:
                current = chunk
                current_header = ""  # 如果开头无标题，则 header 为空

    # 添加最后一段
    if current:
        rechunked.append(current)
        rechunked_headers.append(current_header)

    return rechunked, rechunked_headers

Ontology = ["舰艇", "系统", "设备", "零部件", "故障", "修理", "维修项目","操作项目","保养"]
Relationships = ["包含", "组成", "解决", "用于", "使用","具有"]

# 节点类型及其属性定义

NODE_TYPES = {
    "舰艇": ["名称","型号", "弦号", "设计单位", "总装工厂", "所属部队"],
    "系统": ["名称", "技术责任单位", "生产单位", "级别"],
    "设备": ["名称", "型号", "技术责任单位", "生产单位", "技术状态", "生产日期", "启用时间","运行时间","运行条件"],
    "零部件": ["名称", "型号", "技术责任单位", "生产单位"],
    "故障": ["故障现象名称", "故障原因", "故障影响", "故障类型", "发生时间", "处置情况", "修复情况"],
    "修理": ["修理方案","修理人员", "修理时间", "修理工具", "备品备件", "修理类型", "维修项目"],
    "维修项目": ["名称","维修项目编号", "组成编码", "维修设备名称", "维修间隔期", "维修级别", "页码", "安全防护要求", "安全注意事项","工具","备品备件","消耗材料","人力人员","修理步骤"],
    "操作项目": ["名称", "操作项目编号", "安全防护", "注意事项","保障设施、保障设备（工具）", "人力人员", "操作步骤"],
    "保养" : ["保养人员","保养时间","保养工具","保养频率"]
}

Ontology_DESCRIPTION = {
    "舰艇": ["代表一艘完整海上作战或保障平台的实体，是所有系统、设备和零部件的最高层级载体。区别于“系统”或“设备”，舰艇具有独立的型号、弦号和部队归属，不可作为其他舰艇的组成部分。"],
    "系统": ["舰艇中实现某一类功能（如动力、导航、通信）的集成化功能单元，由多个设备协同组成。与“设备”不同，系统本身不直接对应物理装置，而是逻辑/功能层面的集合；与“舰艇”不同，系统不具备独立航行能力或部队归属。"],
    "设备": ["安装在舰艇上、具有独立型号和运行状态的物理装置，可被启停、监控运行时间，并可能产生故障。区别于“零部件”，设备是可独立更换或维修的功能单元；区别于“系统”，设备是具体硬件而非功能集合。"],
    "零部件": ["构成设备的不可再拆分的基础物理元件（如轴承、电路板、密封圈），通常无独立运行状态或启用时间。与“设备”关键区别在于：零部件不能单独运行，也不记录运行时间或技术状态变更。"],
    "故障": ["设备或系统在运行中出现的异常状态或失效事件，必须包含具体现象（如“无法启动”）、原因（如“电源短路”）及影响。区别于“修理”或“保养”，故障是问题本身，而非应对措施；不包含操作步骤或人员信息。"],
    "修理": ["针对已发生故障所执行的一次性修复行为，包含具体时间、人员、工具、方案和所关联的维修项目。区别于“保养”，修理是响应式、非周期性的；区别于“维修项目”，修理是实际发生的实例，而非标准化规程。"],
    "维修项目": ["标准化、可重复执行的维修任务规程，规定了某类设备或系统在特定条件下应如何维修，包含编号、步骤、材料、安全要求等。区别于“修理”，维修项目是模板或规范，而非具体事件；不包含实际发生时间或人员。"],
    "操作项目": ["舰艇正常运行中需执行的标准操作流程（如启动主机、校准雷达），强调操作步骤与安全保障。区别于“维修项目”，操作使用项目用于日常使用而非维护"],
    "保养" : ["按计划周期性执行的预防性维护活动（如润滑、清洁、检测），目的是延缓性能退化。"]
}

RELATIONSHIP_TYPES = {
    "包含": ["舰艇包含系统", "系统包含设备","设备包含零部件","修理包含维修项目"],
    "解决": ["修理解决故障", "维修项目解决故障"],
    "使用": ["设备使用操作项目", "设备使用保养","设备使用维修项目"],
    "具有": ["设备具有故障","零部件具有故障"]
}

def create_extraction_prompt(
    text: str,
    ontology: List[str],
    Ontology_DESCRIPTION: Dict[str, List[str]],
    relationships: List[str],
    node_types: Dict[str, List[str]],
    relationship_types: Dict[str, List[str]]
) -> str:
    # 构建已有本体的定义（用于上下文）
    ontology_def_descriptions = []
    for etype in ontology:
        clean_etype = etype.strip()
        if clean_etype in Ontology_DESCRIPTION:
            desc = "；".join(Ontology_DESCRIPTION[clean_etype])
            ontology_def_descriptions.append(f"- {clean_etype}：{desc}")
        else:
            ontology_def_descriptions.append(f"- {clean_etype}：无明确定义")

    # 构建关系示例
    rel_examples = []
    for rtype, examples in relationship_types.items():
        for ex in examples[:1]:
            rel_examples.append(f"  - {ex}")

    prompt = f"""你是一个专业的舰艇维修领域工程师。请基于给出的本体和属性等信息，抽取出实体、关系以及相关属性值。

> ⚠️ 重要说明：
> - **本体（Ontology Class）是抽象概念类别**，是对特定领域中概念体系的形式化、结构化规范，用于明确定义该领域内的实体类型（类）。


### 已有本体类型：
{chr(10).join(ontology_def_descriptions)}

### 已有关系类型：
- {"、".join(relationships)}
典型用法示例：
{chr(10).join(rel_examples)}

### 待分析文本：
{text.strip()}

### 你的任务：
1、根据给出的本体、关系以及相应的属性，从文本中抽取出所有可能的实体、关系以及相关属性值。
2、不要随意添加无效属性，抽取的实体属于本体范围内，关系属于关系范围内。、

### 输出格式：
请输出json格式内容，不要输出其他格式和额外内容
{{
  "entities": [
    {{
      "type": "设备",
      "name": "1 号主机冷却系统",
      "properties": {{
        "名称": "1 号主机冷却系统",
        "型号": "",
        "技术责任单位": "XX 舰艇保障大队",
        "生产单位": "",
        "技术状态": "",
        "生产日期": "",
        "启用时间": "",
        "运行时间": "",
        "运行条件": ""
      }}
    }},
    {{
      "type": "故障",
      "name": "1 号主机冷却系统温度异常升高",
      "properties": {{
        "故障现象": "1 号主机冷却系统温度异常升高",
        "故障原因": "根据 A 设备发生的温度异常升高现象,结合相关技术手册与历史故障记录,分析如下:
                (一)冷却液循环泵可能存在异常,导致流量不足;  
                (二)热交换器内部堵塞或外部散热片污损,影响散热效率;  
                (三)温度传感器或控制系统信号异常,导致误报警。
",
        "故障影响": "",
        "故障类型": "",
        "发生时间": "2013 年 05 月 7 日 9 时 23 分 37 秒",
        "处置情况": " ",
        "修复情况": ""，
        “”
      }}
    }}
  ],
  "relationships": [
    {{
      "type": "具有",
      "from_entity": "1 号主机冷却系统",
      "to_entity": "1 号主机冷却系统温度异常升高"
    }}
  ]
}}

注意：
- 不要生成一些关系，如果没有就不要推荐
- 如果文本没有符合的关系和实体，不要进行强行抽取
"""

    return prompt

def extract_text_node_relationship(
    text: List[str],
    ontology: List[str],
    Ontology_DESCRIPTION: Dict[str, List[str]],
    relationships: List[str],
    node_types: Dict[str, List[str]],
    relationship_types: Dict[str, List[str]]
):
    new_entities_result = []
    new_relationships_result = []

    for ins in text:
        final_prompt = create_extraction_prompt(
            text=ins,
            ontology=ontology,
            Ontology_DESCRIPTION=Ontology_DESCRIPTION,
            relationships=relationships,
            relationship_types=relationship_types,
            node_types=node_types
        )
        res_str = OpenaiAPI.openai_chat(final_prompt,timeout=180)

        # 处理 API 调用失败
        if res_str is None:
            print("⚠️ 跳过当前 chunk：API 调用失败")
            continue

        # 尝试解析 JSON
        try:
            res = json.loads(res_str)
        except json.JSONDecodeError as e:
            print(f"❌ JSON 解析失败: {e}")
            print(f"原始响应内容: {repr(res_str)}")
            continue

        # 安全访问字段
        new_entities = res.get("entities", [])
        new_relationships = res.get("relationships", [])
        for entity in new_entities:
                if "properties" in entity and isinstance(entity["properties"], dict):
                    entity["properties"]["切片"] = ins


        new_entities_result.extend(new_entities)
        new_relationships_result.extend(new_relationships)

    # 可选：最后汇总输出
    # print("\n=== 最终结果 ===")
    # print("生成文本本体:", new_entities_result)
    # print("生成文本关系:", new_relationships_result)
    return new_entities_result,new_relationships_result


# if __name__ == "__main__":
#     text = """
#      # 1）螺旋桨本体

# 功能与特性：能量转换的核心。它将主机的旋转动能转化为水流的反作用力（推力）。其特性由材料（如镍铝青铜、锰青铜）、桨叶数（3-6叶）、盘面比、螺距比等决定，直接影响效率、空泡和振动性能。

# 相互关系：直接安装在尾轴（螺旋桨轴） 末端，其扭矩由尾轴传递，其推力通过推力轴承传递给船体。

# 2）轴系

# 功能与特性：动力传递的桥梁。包括：
# s
# 推力轴及推力轴承：承受螺旋桨产生的巨大轴向推力，并将其传递给船体结构，是决定船舶能否前进、后退的关键。

# 中间轴：连接主机输出端与推力轴/尾轴。

# 尾轴（螺旋桨轴）：穿过尾轴管，末端安装螺旋桨，工作环境恶劣（浸没海水中），通常需包覆铜套防腐。

# 相互关系：将主机的旋转输出与螺旋桨本体刚性连接，形成一个完整的动力传递链。推力轴承是轴系与船体结构之间的关键受力界面。

# 3）尾轴管与密封系统功能与特性：

# 尾轴管：固定于船体尾端，支撑尾轴并形成从船内（机舱）到船外（海水）的通道。

# 密封系统（首尾密封）：绝对关键的安全设备。防止海水沿尾轴流入机舱（尾密封），同时防止润滑油泄漏（首尾密封均有此功能）。通常采用唇式密封或面式密封，材料耐磨、耐腐蚀。

# 相互关系：为尾轴提供支撑和润滑（油润滑或水润滑），其密封系统直接保护尾轴并确保机舱安全，与轴系和船体结构紧密集成。

# 4）毂帽与导流罩功能与特性：

# 毂帽：安装在螺旋桨毂后端，呈流线型，用于减少桨毂后部产生的涡流阻力，提高效率。

# 导流罩（如桨毂涡流消除器）：安装在桨毂前方，改善流入桨叶的水流状态，尤其适用于肥大船型，能提升效率、减少振动。

# 相互关系：直接安装在螺旋桨本体的毂上，是其水动力性能的优化附件。

# 系统相互关系总结：

# 主机驱动轴系旋转，轴系末端的尾轴带动螺旋桨本体旋转。螺旋桨产生的推力通过尾轴传递至推力轴承，最终作用在船体上使其运动。尾轴管及密封系统为这一旋转运动提供支撑、润滑和至关重要的水密保障。毂帽和导流罩则作为螺旋桨本体的附件，优化其水动力性能。所有子设备协同工作的核心目标，是确保主机功率被高效、平稳、可靠地转化为推进船舶前进的有效推力。 任何一环失效（如密封泄漏、轴承损坏）都将导致整个推进系统瘫痪。
# """ 
#     content = []
#     content.append(text)
#     extract_text_node_relationship(content,ontology=Ontology,Ontology_DESCRIPTION=Ontology_DESCRIPTION,relationships=Relationships,relationship_types=RELATIONSHIP_TYPES,node_types=NODE_TYPES)

