import pandas as pd
from io import StringIO  # 1. 导入 StringIO
import json
import requests
import json
import os
from extract_util import safe_json_loads,highlight_pdf_file,get_content_bbox
import re
import asyncio
from bs4 import BeautifulSoup
import logging
# 配置 logger
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# ================= 函数定义 =================
def save_to_file(data, filepath, is_json=True):
    """通用保存函数"""
    try:
        with open(filepath, "w", encoding="utf-8") as f:
            if is_json:
                json.dump(data, f, ensure_ascii=False, indent=2)
            else:
                f.write(data)
        print(f"✅ 已保存: {filepath}")
        return True
    except Exception as e:
        print(f"❌ 保存失败 {filepath}: {e}")
        return False
def is_fault_table_by_first_tr(table_body: str) -> bool:
    if not isinstance(table_body, str):
        return False
    
    # 非贪婪匹配第一个 <tr> ... </tr>
    match = re.search(r'<tr[^>]*>(.*?)</tr>', table_body, re.IGNORECASE | re.DOTALL)
    if not match:
        
        return False
    
    first_tr_html = match.group(1)
    
    # 提取所有 <td> 或 <th> 中的文本（简单去标签）
    # 更健壮的做法是递归去除标签，但这里用正则足够
    text_in_first_tr = re.sub(r'<[^>]+>', '', first_tr_html)  # 去除所有 HTML 标签
    
    # 统一转为字符串并检查关键词
    text_lower = text_in_first_tr.lower()
    keywords = ["故障现象", "故障原因", "维修项目"]
    return all(kw in text_lower for kw in keywords)

# ================= 新增：识别故障表类型 =================
def get_fault_table_type(table_body: str):
    """
    返回故障表类型：
      'type1' -> 包含 [故障现象, 故障原因, 维修项目]
      'type2' -> 包含 [故障现象, 可能原因, 排除方法]
      None    -> 非故障表
    """
    if not isinstance(table_body, str):
        return None

    match = re.search(r'<tr[^>]*>(.*?)</tr>', table_body, re.IGNORECASE | re.DOTALL)
    if not match:
        return None

    text_lower = re.sub(r'<[^>]+>', '', match.group(1)).lower()

    if all(kw in text_lower for kw in ["故障现象", "故障原因", "维修项目"]):
        return "type1"
    elif all(kw in text_lower for kw in ["故障现象", "可能原因", "排除方法"]):
        return "type2"
    elif all(kw in text_lower for kw in ["故障外部现象", "故障可能原因", "操作员指导","排除方法"]):
        return "type3"
    return None


# ================= 修改：返回时携带 table_type =================
def get_guzhang_chunk_bbox_and_removed_data(data):
    results = []          # 元素格式: {"group": [...], "table_type": "type1"/"type2"}
    ids_to_remove = set()
    record_map = {item["id"]: item for item in data}

    for ins in data:
        if ins.get("type") == "table":
            table_type = get_fault_table_type(ins.get("table_body", ""))

            if table_type is not None:
                current_id = ins["id"]
                prev_ids = [current_id - 1]
                group_ids = [pid for pid in prev_ids if pid >= 1] + [current_id]
                ids_to_remove.update(group_ids)
                group = [record_map.get(pid) for pid in prev_ids if pid >= 1] + [ins]
                results.append({"group": group, "table_type": table_type})  # ← 携带类型

    filtered_data = [item for item in data if item["id"] not in ids_to_remove]
    return results, filtered_data

import re

def _expand_rowspans(content: str) -> list[list[str]]:
    """
    将含 rowspan 的 HTML 表格展开为完整二维列表。
    每个被合并的单元格都会被填充为原始内容。
    """
    all_rows = re.findall(r"<tr>(.*?)</tr>", content, flags=re.DOTALL)
    if not all_rows:
        return []

    matrix  = []
    pending = {}  # {col_index: (remaining_rows, cell_text)}

    for row_html in all_rows:
        cells    = re.findall(r"<td([^>]*)>(.*?)</td>", row_html, flags=re.DOTALL)
        row      = []
        col      = 0
        cell_idx = 0

        while cell_idx < len(cells) or any(c >= col for c in pending):
            if col in pending:
                # 该列被上方 rowspan 占用，直接填充
                remaining, text = pending[col]
                row.append(text)
                if remaining - 1 > 0:
                    pending[col] = (remaining - 1, text)
                else:
                    del pending[col]
                col += 1

            elif cell_idx < len(cells):
                attrs_str, text = cells[cell_idx]
                text = text.strip()

                rowspan_m = re.search(r'rowspan\s*=\s*["\']?(\d+)', attrs_str)
                rowspan   = int(rowspan_m.group(1)) if rowspan_m else 1

                row.append(text)
                if rowspan > 1:
                    pending[col] = (rowspan - 1, text)

                col      += 1
                cell_idx += 1
            else:
                break

        matrix.append(row)

    return matrix


def parse_table_content(content: str, chunk_size: int = 10) -> list[dict]:
    """
    先展开 rowspan，再按 chunk_size 切分，分批解析后汇总返回。
    """
    matrix = _expand_rowspans(content)
    if not matrix:
        print("⚠️ 未找到有效表格，返回空列表")
        return []

    header    = matrix[0]   # 表头
    data_rows = matrix[1:]  # 数据行（rowspan 已展开）
    total     = len(data_rows)

    print(f"📋 展开后共 {total} 条数据行，按每批 {chunk_size} 行切分处理")

    def _to_html(rows: list[list[str]]) -> str:
        def row_html(cells):
            return "<tr>" + "".join(f"<td>{c}</td>" for c in cells) + "</tr>"
        return "<table>" + row_html(header) + "".join(row_html(r) for r in rows) + "</table>"

    result_list = []

    for chunk_start in range(0, total, chunk_size):
        chunk      = data_rows[chunk_start: chunk_start + chunk_size]
        chunk_html = _to_html(chunk)

        try:
            chunk_df_list = pd.read_html(StringIO(chunk_html), header=0)
            if not chunk_df_list:
                print(f"  ⚠️ 切片 [{chunk_start}:{chunk_start+chunk_size}] 解析失败，跳过")
                continue

            chunk_result = json.loads(
                chunk_df_list[0].to_json(orient='records', force_ascii=False)
            )
            result_list.extend(chunk_result)
            print(f"  ✅ 切片 [{chunk_start}:{chunk_start+chunk_size}] 解析 {len(chunk_result)} 条")

        except Exception as e:
            print(f"  ❌ 切片 [{chunk_start}:{chunk_start+chunk_size}] 处理异常: {e}")
            continue

    print(f"共汇总 {len(result_list)} 条记录")
    return result_list
# ================= 修改：主函数按类型分支处理 =================
async def get_guzhang_node_relation(input_pdf_path, data, prefix_url="http://192.168.0.46:59085", device="发动机"):
    for idx, item in enumerate(data, start=1):
        item["id"] = idx

    guzhang_groups, cleaned_data = get_guzhang_chunk_bbox_and_removed_data(data)
    print(f"抽取的故障表总数量为：{len(guzhang_groups)}")

    if not guzhang_groups:
        print("未找到符合条件的组成表格，跳过处理。")
        all_entity_results, all_relation_results = [], []

    else:
        all_entity_results, all_relation_results = [], []

        for i, item in enumerate(guzhang_groups):
            compose     = item["group"]
            table_type  = item["table_type"]

            try:
                print(f"开始处理第 {i+1} 个故障表，类型: {table_type}")
                content, highlight_list = get_content_bbox(compose)
                print("故障表切片内容为：", content[:200])

                # df_list = pd.read_html(StringIO(content), header=0)
                # if not df_list:
                #     print(f"  ⚠️ 第 {i+1} 组未解析到表格，跳过")
                #     continue

                # df = df_list[0]
                # df.dropna(how='all', inplace=True)
                # result_list = json.loads(df.to_json(orient='records', force_ascii=False))
                result_list = parse_table_content(content, chunk_size=10)
                if not result_list:
                    continue
                for ins in result_list:
                    print(ins)
                    print(1111111111111111111111)
                highlight_filename, most_common_page, filename = highlight_pdf_file(input_pdf_path, highlight_list)
                url = f"{prefix_url}/upload/{highlight_filename}#page={most_common_page}"
                logger.info(f"处理完成，高亮文件：{highlight_filename}，高亮页码：{most_common_page}")
                # ---------- type1: 故障现象 / 故障原因 / 维修项目 ----------
                if table_type == "type1":
                    for ins in result_list:
                        # 安全获取字段，如果是 None 则转为空字符串
                        guzhang_xianxiang = ins.get("故障现象") or ""
                        guzhang_yuanyin = ins.get("故障原因") or ""
                        weixiu_xiangmu = ins.get("维修项目") or ""
                        
                        # 只有当现象或原因不为空时才构建故障名，避免生成 "导致" 这种无意义字符串
                        if guzhang_xianxiang or guzhang_yuanyin:
                            guzhangname = f"{guzhang_yuanyin}导致{guzhang_xianxiang}"
                        else:
                            continue # 如果关键信息缺失，跳过该行

                        source_data_list = [{"filename": filename, "info": content, "url": url}]

                        all_entity_results.append({
                            "type": "故障模式",
                            "properties": {
                                "名称":     guzhangname,
                                "故障现象": guzhang_xianxiang,
                                "故障原因": guzhang_yuanyin,
                                "维修方案": weixiu_xiangmu,
                                "knowledge_source": json.dumps(source_data_list, ensure_ascii=False)
                            }
                        })
                        all_relation_results.append({
                            "type":        "包含故障",
                            "from_entity": f"{device}的维修工作",
                            "to_entity":   guzhangname
                        })

                # ---------- type2: 故障现象 / 可能原因 / 排除方法 ----------
                elif table_type == "type2":
                    for ins in result_list:
                        guzhang_xianxiang = ins.get("故障现象") or ""
                        guzhang_yuanyin = ins.get("可能原因") or ""
                        weixiu_fangfa = ins.get("排除方法") or ""

                        if guzhang_xianxiang or guzhang_yuanyin:
                            guzhangname = f"{guzhang_yuanyin}导致{guzhang_xianxiang}"
                        else:
                            continue

                        source_data_list = [{"filename": filename, "info": content, "url": url}]

                        all_entity_results.append({
                            "type": "故障模式",
                            "properties": {
                                "名称":     guzhangname,
                                "故障现象": guzhang_xianxiang,
                                "故障原因": guzhang_yuanyin,
                                "维修方案": weixiu_fangfa,
                                "knowledge_source": json.dumps(source_data_list, ensure_ascii=False)
                            }
                        })
                        all_relation_results.append({
                            "type":        "包含故障",
                            "from_entity": f"{device}的维修工作",
                            "to_entity":   guzhangname
                        })

                elif table_type == "type3":
                    for ins in result_list:
                        guzhang_xianxiang = ins.get("故障外部现象") or ""
                        guzhang_yuanyin = ins.get("故障可能原因") or ""
                        caozuoyuan_zhidao = ins.get("操作员指导") or ""
                        weixiu_fangfa = ins.get("排除方法") or ""

                        if guzhang_xianxiang or guzhang_yuanyin:
                            guzhangname = f"{guzhang_yuanyin}导致{guzhang_xianxiang}"
                        else:
                            continue

                        source_data_list = [{"filename": filename, "info": content, "url": url}]

                        all_entity_results.append({
                            "type": "故障模式",
                            "properties": {
                                "名称":     guzhangname,
                                "故障现象": guzhang_xianxiang,
                                "故障原因": guzhang_yuanyin,
                                "操作员指导": caozuoyuan_zhidao,
                                "维修方案": weixiu_fangfa,
                                "knowledge_source": json.dumps(source_data_list, ensure_ascii=False)
                            }
                        })
                        all_relation_results.append({
                            "type":        "包含故障",
                            "from_entity": f"{device}的维修工作",
                            "to_entity":   guzhangname
                        })
            except Exception as e:
                print(f"处理第 {i+1} 组时发生未预期错误: {e}")
                continue

        # 设备 → 维修工作指导 节点和关系（两种类型共用）
        all_entity_results.append({
            "type": "维修工作",
            "properties": {"名称": f"{device}的维修工作"}
        })
        all_relation_results.append({
            "type":        "发生故障使用维修工作",
            "from_entity": device,
            "to_entity":   f"{device}维修工作"
        })

    return all_entity_results, all_relation_results



"""
实体：设备、维修工作、故障、维修项目
关系：设备---发生故障使用维修工作--->维修工作
维修工作---包含--->故障
故障---被修复--->维修项目
"""
if __name__ == "__main__":
    file_path = '/storage01/home/hdf/project/wxkgrag/kgextract/output/122-06A0014-B01001_发动机-维修手册_content_list.json'
    input_pdf_path = "/storage01/home/hdf/project/wxkgrag/kgextract/122-06A0014-B01001_发动机-维修手册.pdf"
    output_json_path = '/storage01/home/hdf/project/wxkgrag/kgextract/output/output2/维修细目表抽取结果.json'  # 你可以自定义路径

    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            data = json.load(file)
    except Exception as e:
        print(f"读取 JSON 文件时出错：{e}")
        data = []
    all_entity_results, all_relation_results = asyncio.run(
        get_guzhang_node_relation(input_pdf_path, data=data)
    ) 
    for ins in all_entity_results:
        print(ins)
    length_entity = len(all_entity_results)
    length_relation = len(all_relation_results)

    print(f"抽取{length_entity}个实体，抽取{length_relation}个关系")
    result = {
    "entities": all_entity_results,
    "relations": all_relation_results
    }

    # 保存为 JSON 文件
    try:
        with open(output_json_path, 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=4)
        print(f"结果已保存至：{output_json_path}")
    except Exception as e:
        print(f"保存 JSON 文件时出错：{e}")

