# -*- coding: utf-8 -*-
"""
知识图谱可视化工具
使用networkx和matplotlib绘制知识图谱
"""

import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib import font_manager
import matplotlib.patches as mpatches
from pathlib import Path
import numpy as np


# 设置中文字体
def setup_chinese_font():
    """设置中文字体"""
    # 尝试多种中文字体
    chinese_fonts = [
        'Microsoft YaHei',
        'SimHei',
        'SimSun',
        'KaiTi',
        'FangSong',
        'STXihei',
        'STSong',
        'STKaiti',
        'STFangsong'
    ]

    for font in chinese_fonts:
        try:
            plt.rcParams['font.sans-serif'] = [font]
            plt.rcParams['axes.unicode_minus'] = False
            break
        except:
            continue

    print(f"使用字体: {plt.rcParams['font.sans-serif'][0]}")


# 节点类型颜色映射
NODE_TYPE_COLORS = {
    'ICH_Project': '#FF6B6B',      # 红色 - 项目
    'Category': '#4ECDC4',         # 青色 - 类别
    'Batch': '#95E1D3',            # 绿色 - 批次
    'Inheritor': '#FFD93D',        # 黄色 - 传承人
    'Institution': '#6C5CE7'       # 紫色 - 机构
}

# 节点类型大小映射
NODE_TYPE_SIZES = {
    'ICH_Project': 300,
    'Category': 500,
    'Batch': 350,
    'Inheritor': 200,
    'Institution': 250
}


def load_graph_data(nodes_csv, rels_csv):
    """加载图谱数据"""
    print("正在加载数据...")

    # 读取节点和关系
    nodes_df = pd.read_csv(nodes_csv, encoding='utf-8-sig')
    rels_df = pd.read_csv(rels_csv, encoding='utf-8-sig')

    print(f"  - 节点: {len(nodes_df)}")
    print(f"  - 关系: {len(rels_df)}")

    return nodes_df, rels_df


def build_networkx_graph(nodes_df, rels_df):
    """构建NetworkX图"""
    print("正在构建图...")

    G = nx.DiGraph()  # 有向图

    # 添加节点
    for idx, row in nodes_df.iterrows():
        node_id = row['id']
        label = row['label']
        node_type = row['type']

        # 截断过长的标签
        if len(label) > 10:
            display_label = label[:10] + '...'
        else:
            display_label = label

        G.add_node(
            node_id,
            label=display_label,
            full_label=label,
            node_type=node_type,
            color=NODE_TYPE_COLORS.get(node_type, '#CCCCCC'),
            size=NODE_TYPE_SIZES.get(node_type, 200)
        )

    # 添加边
    for idx, row in rels_df.iterrows():
        source = row['source']
        target = row['target']
        rel_type = row['type']

        if source in G.nodes() and target in G.nodes():
            G.add_edge(source, target, rel_type=rel_type)

    print(f"  - 节点数: {G.number_of_nodes()}")
    print(f"  - 边数: {G.number_of_edges()}")

    return G


def filter_graph_by_type(G, include_types=None):
    """按节点类型过滤图"""
    if include_types is None:
        return G

    nodes_to_keep = [n for n, d in G.nodes(data=True)
                     if d.get('node_type') in include_types]

    return G.subgraph(nodes_to_keep).copy()


def draw_graph(G, output_path, title="知识图谱", layout='spring'):
    """绘制知识图谱"""
    print(f"正在绘制图谱: {title}")

    plt.figure(figsize=(20, 16))

    # 选择布局算法
    if layout == 'spring':
        pos = nx.spring_layout(G, k=2, iterations=50, seed=42)
    elif layout == 'circular':
        pos = nx.circular_layout(G)
    elif layout == 'kamada_kawai':
        pos = nx.kamada_kawai_layout(G)
    elif layout == 'random':
        pos = nx.random_layout(G)
    else:
        pos = nx.spring_layout(G, k=2, iterations=50, seed=42)

    # 按节点类型分组
    node_types = {}
    for node, data in G.nodes(data=True):
        node_type = data.get('node_type', 'Unknown')
        if node_type not in node_types:
            node_types[node_type] = []
        node_types[node_type].append(node)

    # 绘制边
    nx.draw_networkx_edges(
        G, pos,
        alpha=0.3,
        width=0.5,
        edge_color='gray',
        arrows=True,
        arrowsize=10,
        arrowstyle='->,head_width=0.2,head_length=0.3'
    )

    # 按类型绘制节点
    for node_type, nodes in node_types.items():
        color = NODE_TYPE_COLORS.get(node_type, '#CCCCCC')
        size = NODE_TYPE_SIZES.get(node_type, 200)

        nx.draw_networkx_nodes(
            G, pos,
            nodelist=nodes,
            node_color=color,
            node_size=size,
            alpha=0.8,
            edgecolors='white',
            linewidths=1
        )

    # 绘制标签（只对重要节点）
    important_nodes = []
    important_labels = {}

    for node, data in G.nodes(data=True):
        node_type = data.get('node_type')
        # 只显示类别、批次和部分重要节点的标签
        if node_type in ['Category', 'Batch'] or (
            node_type == 'ICH_Project' and data.get('size', 0) > 400
        ):
            important_nodes.append(node)
            important_labels[node] = data.get('label', node)

    if len(important_nodes) <= 100:  # 节点不多时显示所有标签
        nx.draw_networkx_labels(
            G, pos,
            labels=important_labels,
            font_size=8,
            font_weight='bold',
            font_family='sans-serif'
        )
    else:
        # 节点太多时只显示类别标签
        category_labels = {n: d['label'] for n, d in G.nodes(data=True)
                          if d.get('node_type') == 'Category'}
        nx.draw_networkx_labels(
            G, pos,
            labels=category_labels,
            font_size=10,
            font_weight='bold'
        )

    # 图例
    legend_patches = []
    for node_type, color in NODE_TYPE_COLORS.items():
        if node_type in node_types:
            patch = mpatches.Patch(color=color, label=node_type)
            legend_patches.append(patch)

    plt.legend(
        handles=legend_patches,
        loc='upper right',
        fontsize=12,
        framealpha=0.9
    )

    plt.title(title, fontsize=16, fontweight='bold', pad=20)
    plt.axis('off')
    plt.tight_layout()

    # 保存图片
    plt.savefig(output_path, dpi=150, bbox_inches='tight')
    print(f"  - 保存到: {output_path}")
    plt.close()


def draw_subgraphs(G, output_dir):
    """绘制子图（按节点类型分组）"""
    print("\n正在绘制子图...")

    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    # 1. 只显示项目和类别
    print("  1. 项目-类别关系图...")
    G1 = filter_graph_by_type(G, ['ICH_Project', 'Category'])
    draw_graph(
        G1,
        output_dir / 'kg_project_category.png',
        title='非遗项目与类别关系',
        layout='spring'
    )

    # 2. 只显示项目和传承人
    print("  2. 项目-传承人关系图...")
    G2 = filter_graph_by_type(G, ['ICH_Project', 'Inheritor'])
    if G2.number_of_nodes() > 0:
        draw_graph(
            G2,
            output_dir / 'kg_project_inheritor.png',
            title='非遗项目与传承人关系',
            layout='spring'
        )

    # 3. 只显示项目、类别和批次
    print("  3. 项目-类别-批次关系图...")
    G3 = filter_graph_by_type(G, ['ICH_Project', 'Category', 'Batch'])
    draw_graph(
        G3,
        output_dir / 'kg_project_category_batch.png',
        title='非遗项目、类别与批次关系',
        layout='kamada_kawai'
    )

    # 4. 完整图谱（抽样显示）
    print("  4. 完整知识图谱...")
    if G.number_of_nodes() > 500:
        # 节点太多时，只显示连接度高的节点
        degrees = dict(G.degree())
        high_degree_nodes = [n for n, d in degrees.items() if d >= 3]
        G_sample = G.subgraph(high_degree_nodes).copy()
        draw_graph(
            G_sample,
            output_dir / 'kg_full_sampled.png',
            title=f'完整知识图谱（抽样，显示{G_sample.number_of_nodes()}个节点）',
            layout='spring'
        )
    else:
        draw_graph(
            G,
            output_dir / 'kg_full.png',
            title='完整知识图谱',
            layout='spring'
        )


def print_statistics(G):
    """打印图统计信息"""
    print("\n" + "="*60)
    print("图谱统计信息")
    print("="*60)

    print(f"\n节点总数: {G.number_of_nodes()}")
    print(f"边总数: {G.number_of_edges()}")

    # 按类型统计节点
    print("\n节点类型分布:")
    node_types = {}
    for node, data in G.nodes(data=True):
        node_type = data.get('node_type', 'Unknown')
        node_types[node_type] = node_types.get(node_type, 0) + 1

    for node_type, count in sorted(node_types.items()):
        percentage = (count / G.number_of_nodes() * 100)
        print(f"  - {node_type}: {count} ({percentage:.1f}%)")

    # 按类型统计边
    print("\n关系类型分布:")
    rel_types = {}
    for u, v, data in G.edges(data=True):
        rel_type = data.get('rel_type', 'Unknown')
        rel_types[rel_type] = rel_types.get(rel_type, 0) + 1

    for rel_type, count in sorted(rel_types.items()):
        percentage = (count / G.number_of_edges() * 100)
        print(f"  - {rel_type}: {count} ({percentage:.1f}%)")

    # 连接度统计
    degrees = [d for n, d in G.degree()]
    print(f"\n连接度统计:")
    print(f"  - 平均连接度: {np.mean(degrees):.2f}")
    print(f"  - 最大连接度: {max(degrees)}")
    print(f"  - 最小连接度: {min(degrees)}")

    # 找出连接度最高的节点
    top_nodes = sorted(G.degree(), key=lambda x: x[1], reverse=True)[:10]
    print(f"\n连接度最高的10个节点:")
    for node, degree in top_nodes:
        node_data = G.nodes[node]
        label = node_data.get('full_label', node)
        node_type = node_data.get('node_type', '')
        print(f"  - [{node_type}] {label}: {degree}个连接")

    print("="*60)


def visualize_kg(nodes_csv, rels_csv, output_dir):
    """可视化知识图谱"""
    print("="*60)
    print("知识图谱可视化工具")
    print("="*60)

    # 设置中文字体
    setup_chinese_font()

    # 加载数据
    nodes_df, rels_df = load_graph_data(nodes_csv, rels_csv)

    # 构建图
    G = build_networkx_graph(nodes_df, rels_df)

    # 打印统计信息
    print_statistics(G)

    # 绘制子图
    draw_subgraphs(G, output_dir)

    print("\n" + "="*60)
    print("可视化完成！")
    print("="*60)


if __name__ == '__main__':
    # 输入文件
    nodes_csv = r'E:\Project\2026_KG_ICH\dofile\kg_project\output\nodes.csv'
    rels_csv = r'E:\Project\2026_KG_ICH\dofile\kg_project\output\rels.csv'

    # 输出目录
    output_dir = r'E:\Project\2026_KG_ICH\dofile\kg_project\output\visualizations'

    # 执行可视化
    visualize_kg(nodes_csv, rels_csv, output_dir)
