"""
元数据处理模块
提供元数据管理、查询、图谱分析和非结构化数据处理的核心功能
"""

import ast
import contextlib
import json
import logging
import re
import time
from typing import Any

from flask import current_app
from minio import S3Error
from app.core.llm.llm_service import llm_client as llm_call  # 导入core/llm模块的函数
from app.services.neo4j_driver import neo4j_driver

logger = logging.getLogger("app")


def serialize_neo4j_object(obj):
    """
    将Neo4j对象转换为可JSON序列化的格式

    Args:
        obj: Neo4j节点或属性值

    Returns:
        序列化后的对象
    """
    if hasattr(obj, "year"):  # DateTime对象
        # 将Neo4j DateTime转换为字符串
        return (
            obj.strftime("%Y-%m-%d %H:%M:%S") if hasattr(obj, "strftime") else str(obj)
        )
    elif hasattr(obj, "__dict__"):  # 复杂对象
        return str(obj)
    else:
        return obj


def serialize_node_properties(node):
    """
    将Neo4j节点属性序列化为可JSON化的字典

    Args:
        node: Neo4j节点对象

    Returns:
        dict: 序列化后的属性字典
    """
    properties = {}
    for key, value in dict(node).items():
        properties[key] = serialize_neo4j_object(value)
    return properties


def get_formatted_time():
    """获取格式化的当前时间"""
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


def get_tags_by_ids(tag_ids: list) -> list:
    """
    根据标签ID列表获取标签详情

    Args:
        tag_ids: 标签ID列表

    Returns:
        标签详情列表，每个元素包含 {id, name_zh, name_en}
    """
    if not tag_ids:
        return []

    try:
        with neo4j_driver.get_session() as session:
            query = """
            MATCH (t:DataLabel)
            WHERE id(t) IN $tag_ids
            RETURN id(t) as id, t.name_zh as name_zh, t.name_en as name_en
            """
            result = session.run(query, {"tag_ids": tag_ids})
            tags = []
            for record in result:
                tags.append(
                    {
                        "id": record["id"],
                        "name_zh": record.get("name_zh") or "",
                        "name_en": record.get("name_en") or "",
                    }
                )
            return tags
    except Exception as e:
        logger.warning(f"获取标签详情失败: {e}")
        return []


def convert_tag_ids_to_tags(data: dict) -> dict:
    """
    将数据中的 tag_ids 字段转换为 tags 字段

    处理 new_meta, old_meta, candidates 中的 tag_ids

    Args:
        data: 包含 new_meta, old_meta, candidates 的字典

    Returns:
        转换后的字典，tag_ids 被替换为 tags
    """
    if not data:
        return data

    result = dict(data)

    # 处理 new_meta
    if "new_meta" in result and result["new_meta"]:
        new_meta = dict(result["new_meta"])
        if "tag_ids" in new_meta:
            new_meta["tags"] = get_tags_by_ids(new_meta.pop("tag_ids", []))
        result["new_meta"] = new_meta

    # 处理 old_meta
    if "old_meta" in result and result["old_meta"]:
        old_meta = dict(result["old_meta"])
        if "tag_ids" in old_meta:
            old_meta["tags"] = get_tags_by_ids(old_meta.pop("tag_ids", []))
        result["old_meta"] = old_meta

    # 处理 candidates
    if "candidates" in result and result["candidates"]:
        new_candidates = []
        for cand in result["candidates"]:
            cand_copy = dict(cand)
            if "tag_ids" in cand_copy:
                cand_copy["tags"] = get_tags_by_ids(cand_copy.pop("tag_ids", []))
            new_candidates.append(cand_copy)
        result["candidates"] = new_candidates

    return result


def translate_and_parse(content):
    """
    翻译内容并返回结果

    Args:
        content: 需要翻译的内容

    Returns:
        list: 包含翻译结果的列表
    """
    translated_text = llm_call(content)
    if not translated_text:
        return [content]

    return [str(translated_text).strip()]


# 为保持原有功能，保留旧的llm_client函数
def llm_client(content):
    """调用LLM服务进行中英文翻译，返回结果"""
    # 直接调用基础服务层的翻译函数
    return llm_call(content)


def infer_column_type(df):
    try:
        # 列名
        res = df.columns.to_list()
        columns = ",".join(res)

        from app.core.llm.deepseek_client import chat_completions_create, create_llm_client

        client = create_llm_client()
        response = chat_completions_create(
            client,
            messages=[
                {
                    "role": "system",
                    "content": (
                        "你是一个PostgreSQL数据库专家，精通PostgreSQL所有数据类型和最佳实践"
                    ),
                },
                {
                    "role": "user",
                    "content": (
                        "请根据以下数据表内容："
                        + str(df.head(n=6))
                        + "其列名为"
                        + columns
                        + "，帮我判断每个列最合适的PostgreSQL数据类型。请注意以下要求："
                        + (
                            "1. 对于文本数据，使用varchar并给出合适长度，如varchar(50)、"
                            "varchar(255)等"
                        )
                        + "2. 对于整数，根据数值范围选择smallint、integer或bigint"
                        + (
                            "3. 对于小数，如果是金额相关字段使用numeric(15,2)，"
                            "其他小数使用numeric(18,6)"
                        )
                        + "4. 对于日期时间，根据实际情况选择date、time或timestamp"
                        + "5. 对于布尔值，使用boolean类型"
                        + "6. 如果是JSON数据，使用jsonb类型"
                        + (
                            "请以列表格式返回，列表的顺序要与输入列名一致，如："
                            "['varchar(255)', 'integer', "
                            "'numeric(15,2)', 'timestamp']"
                            "只返回列表，不要有任何其他说明文字"
                        )
                    ),
                },
            ],
            max_tokens=1024,
            temperature=0.1,
        )
        content = response.choices[0].message.content
        if not content:
            raise ValueError("LLM 返回内容为空")
        res = str(content).strip("`").replace("python", "").strip("`").strip()

        # 使用 ast.literal_eval 函数将字符串转换为列表
        result_list = ast.literal_eval(res)
        return result_list
    except Exception as e:
        logger.error(f"列类型推断失败: {str(e)}")
        # 返回一个空列表或默认类型列表，保持返回类型一致
        return ["varchar(255)"] * len(df.columns) if not df.empty else []


def meta_list(
    page,
    page_size,
    search="",
    name_en_filter=None,
    name_zh_filter=None,
    category_filter=None,
    create_time_filter=None,
    tag_filter=None,
):
    """
    获取元数据列表

    Args:
        page: 当前页码
        page_size: 每页数量
        search: 搜索关键词
        name_en_filter: 英文名称过滤
        name_zh_filter: 名称过滤
        category_filter: 分类过滤
        create_time_filter: 时间过滤
        tag_filter: 标签过滤

    Returns:
        tuple: (result_list, total_count)
    """
    try:
        with neo4j_driver.get_session() as session:
            # 构建查询条件
            match_clause = "MATCH (n:DataMeta)"
            optional_match = "OPTIONAL MATCH (n)-[:LABEL]->(t:DataLabel)"
            where_conditions = []
            params: dict = {}

            if search:
                where_conditions.append("n.name_zh CONTAINS $search")
                params["search"] = search

            if name_en_filter:
                where_conditions.append("n.name_en CONTAINS $name_en_filter")
                params["name_en_filter"] = name_en_filter

            if name_zh_filter:
                where_conditions.append("n.name_zh CONTAINS $name_zh_filter")
                params["name_zh_filter"] = name_zh_filter

            if category_filter:
                where_conditions.append("n.category = $category_filter")
                params["category_filter"] = category_filter

            if create_time_filter:
                where_conditions.append("n.create_time CONTAINS $create_time_filter")
                params["create_time_filter"] = create_time_filter

            # 构建主节点的 WHERE 子句
            where_clause = (
                " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
            )

            # 处理 tag_filter - 支持 ID 列表或对象列表
            tag_where_clause = ""
            if tag_filter and isinstance(tag_filter, list):
                tag_ids = []
                for item in tag_filter:
                    if isinstance(item, dict) and "id" in item:
                        tag_ids.append(int(item["id"]))
                    elif isinstance(item, (int, str)):
                        with contextlib.suppress(ValueError, TypeError):
                            tag_ids.append(int(item))
                if tag_ids:
                    tag_where_clause = " WHERE id(t) IN $tag_ids"
                    params["tag_ids"] = tag_ids

            # 计算总数
            if tag_where_clause:
                # 有 tag 过滤时，使用 MATCH 而非 OPTIONAL MATCH
                count_cypher = f"""
                {match_clause}
                {where_clause}
                MATCH (n)-[:LABEL]->(t:DataLabel)
                {tag_where_clause}
                RETURN count(DISTINCT n) as count
                """
            else:
                count_cypher = f"""
                {match_clause}
                {where_clause}
                RETURN count(DISTINCT n) as count
                """
            count_result = session.run(count_cypher, **params)
            count_record = count_result.single()
            total_count = count_record["count"] if count_record else 0

            # 分页查询（page_size 为空时返回全部记录）
            use_pagination = page_size is not None and page_size > 0
            if use_pagination:
                skip = (page - 1) * page_size
                params["skip"] = skip
                params["limit"] = page_size
            if tag_where_clause:
                # 有 tag 过滤时，先匹配符合条件的节点
                cypher = f"""
                {match_clause}
                {where_clause}
                MATCH (n)-[:LABEL]->(t:DataLabel)
                {tag_where_clause}
                WITH DISTINCT n
                {optional_match}
                RETURN n, collect(DISTINCT t) as tags
                ORDER BY n.name_zh
                {"SKIP $skip LIMIT $limit" if use_pagination else ""}
                """
            else:
                cypher = f"""
                {match_clause}
                {where_clause}
                {optional_match}
                RETURN n, collect(DISTINCT t) as tags
                ORDER BY n.name_zh
                {"SKIP $skip LIMIT $limit" if use_pagination else ""}
                """
            result = session.run(cypher, **params)

            # 格式化结果
            result_list = []
            for record in result:
                node = serialize_node_properties(record["n"])
                node["id"] = record["n"].id

                tag_nodes = record.get("tags") or []
                tag_list = []
                for tag in tag_nodes:
                    if tag:
                        tag_list.append(
                            {
                                "id": tag.id,
                                "name_zh": tag.get("name_zh", ""),
                                "name_en": tag.get("name_en", ""),
                            }
                        )
                node["tag"] = tag_list
                result_list.append(node)

            return result_list, total_count
    except Exception as e:
        logger.error(f"获取元数据列表失败: {str(e)}")
        raise


def handle_id_unstructured(node_id):
    """处理非结构化数据节点"""
    try:
        # 参数验证
        if node_id is None:
            logger.error("node_id参数不能为None")
            return None

        # 确保node_id为整数
        try:
            node_id_int = int(node_id)
        except (ValueError, TypeError):
            logger.error(f"node_id不是有效的整数: {node_id}")
            return None

        with neo4j_driver.get_session() as session:
            query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
            result = session.run(query, node_id=node_id_int)
            node = result.single()
            if node:
                return serialize_node_properties(node["n"])
            else:
                return None
    except Exception as e:
        logger.error(f"处理非结构化数据节点失败: {str(e)}")
        raise


def get_file_content(minio_client, bucket_name, object_name):
    """从MinIO获取文件内容"""
    try:
        # 获取对象
        response = minio_client.get_object(bucket_name, object_name)

        # 读取内容
        file_content = response.read().decode("utf-8")
        return file_content
    except S3Error as e:
        logger.error(f"MinIO访问失败: {str(e)}")
        raise
    finally:
        response.close()
        response.release_conn()


def parse_text(text):
    """解析文本内容，提取关键信息"""
    # 提取作者信息
    author_match = re.search(r"作者[：:]\s*(.+?)[\n\r]", text)
    author = author_match.group(1) if author_match else ""

    # 提取关键词
    keyword_match = re.search(r"关键词[：:]\s*(.+?)[\n\r]", text)
    keywords = keyword_match.group(1) if keyword_match else ""

    return {"author": author.strip(), "keywords": keywords.strip()}


def parse_keyword(content):
    """解析关键词"""
    if "，" in content:
        return content.split("，")
    elif "," in content:
        return content.split(",")
    elif "、" in content:
        return content.split("、")
    else:
        return [content]


def text_resource_solve(receiver, name_zh, keyword):
    """处理文本资源解析"""
    try:
        # 构建提示词 - 使用简短明确的指令
        prompt = f"{name_zh}"

        # 调用LLM获取英文翻译
        name_en = llm_client(prompt)

        # 提取关键词
        keywords = parse_keyword(keyword)

        # 为每个关键词获取英文翻译
        keywords_en = []
        for kw in keywords:
            # 直接使用关键词作为翻译输入
            kw_en = llm_client(kw)
            keywords_en.append(kw_en)

        # 构建返回数据
        return {
            "name_zh": name_zh,
            "name_en": name_en,
            "keywords": keywords,
            "keywords_en": keywords_en,
        }
    except Exception as e:
        logger.error(f"文本资源处理失败: {str(e)}")
        raise


def meta_kinship_graph(node_id):
    """
    获取元数据亲缘关系图谱

    Args:
        node_id: 元数据节点ID

    Returns:
        dict: 图谱数据
    """
    try:
        # 参数验证
        if node_id is None:
            logger.error("node_id参数不能为None")
            return {"nodes": [], "relationships": []}

        # 确保node_id为整数
        try:
            node_id_int = int(node_id)
        except (ValueError, TypeError):
            logger.error(f"node_id不是有效的整数: {node_id}")
            return {"nodes": [], "relationships": []}

        with neo4j_driver.get_session() as session:
            # 获取节点及其直接关系（可为空）
            cypher = """
            MATCH (n)
            WHERE id(n) = $node_id
            OPTIONAL MATCH (n)-[r]-(m)
            RETURN n, r, m
            """
            result = session.run(cypher, node_id=node_id_int)

            nodes: dict[int, dict] = {}
            relationships: list[dict] = []

            for record in result:
                n_node = record["n"]
                if n_node:
                    source_node = serialize_node_properties(n_node)
                    source_node["id"] = n_node.id
                nodes[source_node["id"]] = source_node

                rel = record.get("r")
                m_node = record.get("m")

                if m_node:
                    target_node = serialize_node_properties(m_node)
                    target_node["id"] = m_node.id
                nodes[target_node["id"]] = target_node

                if rel and n_node and m_node:
                    relationships.append(
                        {
                            "id": rel.id,
                            "source": n_node.id,
                            "target": m_node.id,
                            "type": rel.type,
                        }
                    )

            # 若无关系结果但节点存在，确保节点仍被返回
            if not nodes:
                node_only = session.run(
                    "MATCH (n) WHERE id(n) = $node_id RETURN n",
                    node_id=node_id_int,
                ).single()
                if node_only and node_only["n"]:
                    n_node = node_only["n"]
                    nodes[n_node.id] = {
                        **serialize_node_properties(n_node),
                        "id": n_node.id,
                    }

            return {
                "nodes": list(nodes.values()),
                "relationships": relationships,
            }
    except Exception as e:
        logger.error(f"获取元数据亲缘关系图谱失败: {str(e)}")
        raise


def meta_impact_graph(node_id):
    """
    获取元数据影响关系图谱

    Args:
        node_id: 元数据节点ID

    Returns:
        dict: 图谱数据，包含 nodes 和 lines
    """
    try:
        # 参数验证
        if node_id is None:
            logger.error("node_id参数不能为None")
            return {"nodes": [], "lines": []}

        # 确保node_id为整数
        try:
            node_id_int = int(node_id)
        except (ValueError, TypeError):
            logger.error(f"node_id不是有效的整数: {node_id}")
            return {"nodes": [], "lines": []}

        with neo4j_driver.get_session() as session:
            # 获取所有可达节点和关系
            cypher = """
            MATCH path = (n)-[*1..3]-(m)
            WHERE id(n) = $node_id
            RETURN path
            """
            result = session.run(cypher, node_id=node_id_int)

            # 格式化结果
            nodes = {}
            relationships = set()

            for record in result:
                path = record["path"]

                # 处理路径中的所有节点
                for node in path.nodes:
                    node_dict = serialize_node_properties(node)
                    node_dict["id"] = node.id
                    nodes[node.id] = node_dict

                # 处理路径中的所有关系
                for rel in path.relationships:
                    relationship = (
                        rel.id,
                        rel.start_node.id,
                        rel.end_node.id,
                        rel.type,
                    )
                    relationships.add(relationship)

            # 转换为列表
            nodes_list = list(nodes.values())
            lines_list = [
                {"id": rel[0], "from": str(rel[1]), "to": str(rel[2]), "text": rel[3]}
                for rel in relationships
            ]

            return {"nodes": nodes_list, "lines": lines_list}
    except Exception as e:
        logger.error(f"获取元数据影响关系图谱失败: {str(e)}")
        raise


def parse_entity_relation(text):
    """从文本中解析实体关系"""
    try:
        # 构建提示词
        prompt = f"""
        请从以下文本中提取实体及其关系，以JSON格式返回，格式为:
        [
          {{"entity1": "实体1", "relation": "关系", "entity2": "实体2"}}
        ]

        文本内容:
        {text}
        """

        # 调用LLM获取关系提取结果
        result = llm_client(prompt)

        # 解析JSON结果
        try:
            relations = json.loads(result)
            return relations
        except json.JSONDecodeError:
            logger.error(f"关系提取结果JSON解析失败: {result}")
            return []

    except Exception as e:
        logger.error(f"实体关系提取失败: {str(e)}")
        return []


def handle_txt_graph(node_id, entity, entity_en):
    """处理文本图谱创建"""
    try:
        # 参数验证
        if node_id is None:
            logger.error("node_id参数不能为None")
            return False

        # 确保node_id为整数
        try:
            node_id_int = int(node_id)
        except (ValueError, TypeError):
            logger.error(f"node_id不是有效的整数: {node_id}")
            return False

        # 创建实体节点
        with neo4j_driver.get_session() as session:
            # 查找源节点
            query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
            result = session.run(query, node_id=node_id_int)
            source_record = result.single()
            source_node = source_record["n"] if source_record else None
            if not source_node:
                return False

            # 创建实体节点
            cypher = """
            MERGE (e:Entity {name_zh: $name_zh, name_en: $name_en})
            ON CREATE SET e.create_time = $create_time
            RETURN e
            """

            create_time = get_formatted_time()
            result = session.run(
                cypher, name_zh=entity, name_en=entity_en, create_time=create_time
            )

            entity_record = result.single()
            entity_node = entity_record["e"] if entity_record else None

            # 创建关系
            if source_node and entity_node:
                # 检查关系是否已存在
                rel_check = """
                MATCH (s)-[r:CONTAINS]->(e)
                WHERE id(s) = $source_id AND id(e) = $entity_id
                RETURN r
                """

                rel_result = session.run(
                    rel_check, source_id=source_node.id, entity_id=entity_node.id
                )

                # 如果关系不存在，则创建
                if not rel_result.single():
                    rel_create = """
                        MATCH (s), (e)
                    MATCH (s), (e)
                    WHERE id(s) = $source_id AND id(e) = $entity_id
                    CREATE (s)-[r:CONTAINS]->(e)
                    RETURN r
                    """

                    session.run(
                        rel_create, source_id=source_node.id, entity_id=entity_node.id
                    )

            return True
    except Exception as e:
        logger.error(f"文本图谱处理失败: {str(e)}")
        return False


def solve_unstructured_data(node_id, minio_client, prefix):
    """处理非结构化数据并提取实体关系"""
    try:
        # 获取节点数据
        node_data = handle_id_unstructured(node_id)
        if not node_data:
            logger.error(f"节点不存在: {node_id}")
            return False

        # 获取对象路径
        object_name = node_data.get("url")
        if not object_name:
            logger.error(f"文档路径不存在: {node_id}")
            return False

        # 获取文件内容
        file_content = get_file_content(
            minio_client,
            bucket_name=node_data.get("bucket_name", "dataops"),
            object_name=object_name,
        )

        # 解析文本内容中的实体关系
        relations = parse_entity_relation(
            file_content[:5000]
        )  # 只处理前5000字符，避免过大内容

        # 如果成功提取了关系
        if relations:
            # 更新节点信息
            with neo4j_driver.get_session() as session:
                update_cypher = """
                MATCH (n) WHERE id(n) = $node_id
                SET n.processed = true, n.processTime = $process_time
                RETURN n
                """

                process_time = get_formatted_time()
                session.run(
                    update_cypher, node_id=int(node_id), process_time=process_time
                )

                # 为每个提取的关系创建实体和关系
                for relation in relations:
                    entity1 = relation.get("entity1", "")
                    relation_type = relation.get("relation", "")
                    entity2 = relation.get("entity2", "")

                    if entity1 and entity2 and relation_type:
                        # 翻译实体名称为英文 - 使用简短直接的输入
                        entity1_en = llm_client(entity1)
                        entity2_en = llm_client(entity2)

                        # 创建第一个实体
                        entity1_cypher = """
                        MERGE (e:Entity {name_zh: $name_zh})
                        ON CREATE SET e.name_en = $name_en,
                                      e.create_time = $create_time
                        RETURN e
                        """

                        entity1_result = session.run(
                            entity1_cypher,
                            name_zh=entity1,
                            name_en=entity1_en,
                            create_time=process_time,
                        )
                        entity1_record = entity1_result.single()
                        entity1_node = entity1_record["e"] if entity1_record else None
                        if not entity1_node:
                            continue

                        # 创建第二个实体
                        entity2_cypher = """
                        MERGE (e:Entity {name_zh: $name_zh})
                        ON CREATE SET e.name_en = $name_en,
                                      e.create_time = $create_time
                        RETURN e
                        """

                        entity2_result = session.run(
                            entity2_cypher,
                            name_zh=entity2,
                            name_en=entity2_en,
                            create_time=process_time,
                        )
                        entity2_record = entity2_result.single()
                        entity2_node = entity2_record["e"] if entity2_record else None
                        if not entity2_node:
                            continue

                        # 创建它们之间的关系
                        rel_cypher: Any = """
                        MATCH (e1:Entity), (e2:Entity)
                        WHERE id(e1) = $entity1_id AND id(e2) = $entity2_id
                        MERGE (e1)-[r:`{relation_type}`]->(e2)
                        RETURN r
                        """.replace("{relation_type}", relation_type)

                        session.run(
                            rel_cypher,
                            entity1_id=entity1_node.id,
                            entity2_id=entity2_node.id,
                        )

                        # 创建源节点与实体的关系
                        source_rel1_cypher = """
                        MATCH (s), (e:Entity)
                        WHERE id(s) = $source_id AND id(e) = $entity_id
                        MERGE (s)-[r:CONTAINS]->(e)
                        RETURN r
                        """

                        session.run(
                            source_rel1_cypher,
                            source_id=int(node_id),
                            entity_id=entity1_node.id,
                        )

                        source_rel2_cypher = """
                        MATCH (s), (e:Entity)
                        WHERE id(s) = $source_id AND id(e) = $entity_id
                        MERGE (s)-[r:CONTAINS]->(e)
                        RETURN r
                        """

                        session.run(
                            source_rel2_cypher,
                            source_id=int(node_id),
                            entity_id=entity2_node.id,
                        )

            return True
        else:
            logger.warning(f"未能从文本中提取到实体关系: {node_id}")
            return False

    except Exception as e:
        logger.error(f"处理非结构化数据失败: {str(e)}")
        return False
