import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
from typing import List, Dict, Any, Optional, Tuple
import os
import warnings


class Visualizer:
    """可视化工具，用于绘制风险-效用曲线和参数敏感性分析。"""

    def __init__(self, style: str = 'seaborn-v0_8-whitegrid'):
        plt.style.use(style)
        self._setup_chinese_font()
        self.figures = []

    def _setup_chinese_font(self):
        """设置中文字体，如果系统中存在中文字体则使用，否则回退到默认字体并给出警告"""
        # 常见中文字体名称列表
        chinese_fonts = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Zen Hei',
                         'Noto Sans CJK SC', 'Noto Sans CJK TC', 'STSong', 'FangSong']
        available_fonts = [f.name for f in fm.fontManager.ttflist]

        font_found = None
        for font in chinese_fonts:
            if font in available_fonts:
                font_found = font
                break

        if font_found:
            # 将找到的中文字体添加到字体列表前端
            plt.rcParams['font.sans-serif'] = [font_found] + plt.rcParams['font.sans-serif']
            plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题
        else:
            warnings.warn("未找到中文字体，图表中的中文可能显示为方框。请安装中文字体（如SimHei）。")

    def plot_risk_utility_scatter(self,
                                  results: List[Dict[str, Any]],
                                  risk_col: str = 'risk',
                                  utility_col: str = 'utility',
                                  highlight_best: Optional[Dict] = None,
                                  risk_constraint: Optional[Tuple[float, float]] = None,
                                  title: str = '风险-效用散点图',
                                  xlabel: str = '风险',
                                  ylabel: str = '效用',
                                  save_path: Optional[str] = None):
        """
        绘制风险-效用散点图。

        参数:
            results: 包含风险、效用和参数的字典列表，每个字典必须有 risk_col 和 utility_col 键。
            risk_col: 风险字段名。
            utility_col: 效用字段名。
            highlight_best: 最优解参数字典，将在图上用红星标记。
            risk_constraint: 风险约束范围 (min, max)，将用红色竖线标出。
            title: 图表标题。
            xlabel: x轴标签。
            ylabel: y轴标签。
            save_path: 如果提供，保存图表到该路径。
        """
        risks = [r[risk_col] for r in results]
        utilities = [r[utility_col] for r in results]

        fig, ax = plt.subplots(figsize=(10, 6))
        ax.scatter(risks, utilities, alpha=0.6, label='参数组合')

        if highlight_best:
            best_risk = highlight_best.get(risk_col)
            best_utility = highlight_best.get(utility_col)
            if best_risk is not None and best_utility is not None:
                ax.scatter([best_risk], [best_utility], color='red', marker='*', s=200, label='最优解', zorder=5)

        if risk_constraint:
            ax.axvline(x=risk_constraint[0], color='red', linestyle='--', alpha=0.5, label='风险下限')
            ax.axvline(x=risk_constraint[1], color='red', linestyle='--', alpha=0.5, label='风险上限')

        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
        ax.set_title(title)
        ax.legend()
        ax.grid(True, alpha=0.3)

        self.figures.append(fig)
        if save_path:
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            fig.savefig(save_path, dpi=100, bbox_inches='tight')
        return fig

    def plot_parameter_sensitivity(self,
                                   results: List[Dict[str, Any]],
                                   param_name: str,
                                   risk_col: str = 'risk',
                                   utility_col: str = 'utility',
                                   title: Optional[str] = None,
                                   save_path: Optional[str] = None):
        """
        绘制参数敏感性曲线：参数值 vs 风险/效用。

        参数:
            results: 搜索结果列表。
            param_name: 要分析的参数名。
            risk_col: 风险字段名。
            utility_col: 效用字段名。
            title: 图表标题。
            save_path: 保存路径。
        """
        param_values = [r['params'][param_name] for r in results]
        risks = [r[risk_col] for r in results]
        utilities = [r[utility_col] for r in results]

        fig, axes = plt.subplots(1, 2, figsize=(14, 5))

        # 风险 vs 参数
        axes[0].scatter(param_values, risks, alpha=0.6)
        axes[0].set_xlabel(param_name)
        axes[0].set_ylabel('风险')
        axes[0].set_title(f'{param_name} 对风险的影响')
        axes[0].grid(True, alpha=0.3)

        # 效用 vs 参数
        axes[1].scatter(param_values, utilities, alpha=0.6)
        axes[1].set_xlabel(param_name)
        axes[1].set_ylabel('效用')
        axes[1].set_title(f'{param_name} 对效用的影响')
        axes[1].grid(True, alpha=0.3)

        if title:
            fig.suptitle(title)

        self.figures.append(fig)
        if save_path:
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            fig.savefig(save_path, dpi=100, bbox_inches='tight')
        return fig

    def show(self):
        """显示所有已绘制的图表（用于交互环境）。"""
        plt.show()

    def close_all(self):
        """关闭所有图表，释放内存。"""
        for fig in self.figures:
            plt.close(fig)
        self.figures = []