import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
import matplotlib.font_manager as fm
import seaborn as sns
import scipy.stats as stats
import pingouin as pg
import scikit_posthocs as sp
from statsmodels.stats.diagnostic import lilliefors
from scipy.stats import levene, kruskal
import statsmodels.api as sm
from statsmodels.stats.multicomp import pairwise_tukeyhsd
import warnings
from typing import Optional, Tuple, Union

def descriptive_statistics(df):
    """计算描述性统计量"""
    return df.describe()

def correlation_analysis(df, method='pearson'):
    """计算相关性矩阵"""
    return df.corr(method=method)

def kmeans_clustering(df, n_clusters=3, random_state=42):
    """K均值聚类"""
    scaler = StandardScaler()
    scaled_data = scaler.fit_transform(df.select_dtypes(include=[np.number]))
    kmeans = KMeans(n_clusters=n_clusters, random_state=random_state)
    clusters = kmeans.fit_predict(scaled_data)
    df['cluster'] = clusters
    return df, kmeans

def linear_regression(X, y):
    """线性回归"""
    model = LinearRegression()
    model.fit(X, y)
    predictions = model.predict(X)
    return model, predictions

def time_series_analysis(df, time_column, value_column):
    """时间序列分析"""
    df[time_column] = pd.to_datetime(df[time_column])
    df = df.sort_values(time_column)
    df.set_index(time_column, inplace=True)
    
    # 计算移动平均
    df['moving_average'] = df[value_column].rolling(window=7).mean()
    # 计算同比增长
    df['year_over_year'] = df[value_column].pct_change(periods=365)
    
    return df

def hypothesis_testing(sample1, sample2):
    """假设检验"""
    from scipy import stats
    t_stat, p_value = stats.ttest_ind(sample1, sample2)
    return t_stat, p_value


def check_normality(
    data: Union[np.ndarray, pd.Series],
    sample_size_threshold: int = 5000,
    method: Optional[str] = 'auto',
    alpha: float = 0.05,
    plot: bool = True,
    figsize: Tuple[int, int] = (12, 5),
    bins: Optional[int] = None,
    title: str = "正态性检验"
) -> dict:
    """
    综合正态性检验：自动选择检验方法 + 可视化诊断。

    参数：
        data : 一维数组或Series，待检验数据。
        sample_size_threshold : 样本量阈值，超过此值视为大样本，改用大样本检验。
        method : 指定检验方法。
            'auto' : 自动选择（n<=threshold用shapiro，否则用lilliefors）
            'shapiro' : 强制用Shapiro-Wilk
            'lilliefors' : 强制用Lilliefors (K-S修正)
            'anderson' : 强制用Anderson-Darling（返回统计量和临界值）
        alpha : 显著性水平，用于正态性检验的判断（默认0.05）。
        plot : 是否绘制Q-Q图和直方图。
        figsize : 图形尺寸。
        bins : 直方图组数，None则自动。
        title : 图形总标题。

    返回：
        字典，包含：
            'method' : 实际使用的检验方法
            'statistic' : 检验统计量
            'p_value' : p值（若可用）；对于Anderson-Darling，返回是否拒绝及临界值
            'is_normal' : 是否通过正态性检验（p>=alpha 或统计量小于临界值）
            'fig' : matplotlib Figure对象（若plot=True）
    作者：Sissi
    """
    # 数据预处理：删除缺失值，确保一维
    data = pd.Series(data).dropna().values
    n = len(data)
    if n < 3:
        raise ValueError("样本量至少为3才能进行正态性检验。")

    # 自动选择检验方法
    if method == 'auto':
        if n <= sample_size_threshold:
            method = 'shapiro'
        else:
            method = 'lilliefors'
    elif method not in ['shapiro', 'lilliefors', 'anderson']:
        raise ValueError("method 必须是 'auto', 'shapiro', 'lilliefors' 或 'anderson'")

    result = {'method': method, 'sample_size': n}
    is_normal = None
    p_value = None
    statistic = None

    # 执行检验
    if method == 'shapiro':
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            statistic, p_value = stats.shapiro(data)
        is_normal = p_value >= alpha
        result.update({'statistic': statistic, 'p_value': p_value, 'is_normal': is_normal})

    elif method == 'lilliefors':
        # Lilliefors 返回 (statistic, p_value)
        statistic, p_value = lilliefors(data, dist='norm', pvalmethod='table')
        is_normal = p_value >= alpha
        result.update({'statistic': statistic, 'p_value': p_value, 'is_normal': is_normal})

    elif method == 'anderson':
        # Anderson-Darling 返回 (statistic, critical_values, significance_levels)
        statistic, critical_values, sig_levels = stats.anderson(data, dist='norm')
        # 找到对应 alpha 的临界值
        idx = np.where(sig_levels == alpha * 100)[0]
        if len(idx) > 0:
            crit_val = critical_values[idx[0]]
            is_normal = statistic < crit_val
        else:
            # 若alpha不在预设列表中，取最接近的
            closest_idx = np.argmin(np.abs(sig_levels - alpha * 100))
            crit_val = critical_values[closest_idx]
            is_normal = statistic < crit_val
        result.update({
            'statistic': statistic,
            'critical_values': critical_values,
            'significance_levels': sig_levels,
            'is_normal': is_normal,
            'alpha': alpha
        })

    # 可视化
    if plot:
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)

        # 直方图 + 核密度估计 + 理论正态曲线
        #sns.histplot(data, kde=True, ax=ax1, label='样本直方图')
        ax1.hist(data, bins=bins, density=True, alpha=0.6, color='steelblue', edgecolor='black', label='样本直方图')
        # 核密度估计
        kde = stats.gaussian_kde(data)
        x_range = np.linspace(data.min(), data.max(), 200)
        ax1.plot(x_range, kde(x_range), 'r-', lw=2, label='核密度估计')
        # 理论正态曲线（使用样本均值和标准差）
        mu, sigma = np.mean(data), np.std(data, ddof=1)
        norm_curve = stats.norm.pdf(x_range, mu, sigma)
        ax1.plot(x_range, norm_curve, 'g--', lw=2, label='理论正态分布')
        ax1.set_xlabel('值')
        ax1.set_ylabel('密度')
        ax1.set_title('直方图与密度曲线')
        ax1.legend()

        # Q-Q 图
        stats.probplot(data, dist="norm", plot=ax2)
        ax2.set_title('Q-Q 图')
        ax2.get_lines()[0].set_marker('o')
        ax2.get_lines()[0].set_markersize(4)
        ax2.get_lines()[0].set_markerfacecolor('steelblue')
        ax2.get_lines()[0].set_markeredgecolor('steelblue')
        ax2.get_lines()[1].set_color('red')
        ax2.get_lines()[1].set_linewidth(2)

        plt.suptitle(title, fontsize=14)
        plt.tight_layout()
        result['fig'] = fig
    else:
        result['fig'] = None

    return result


def compare_groups_single_factor(df, group_col, value_col, alpha=0.05, normality_threshold=5000):
    """
    单因素分析：比较不同组（如压低模式）对连续变量（如表达率）的影响。
    对每组调用 check_normality 进行正态性检验，自动选择参数或非参数检验，
    并输出效应量和事后检验。

    参数：
        df : pandas DataFrame
        group_col : str，分组列名
        value_col : str，数值列名
        alpha : float，显著性水平
        normality_threshold : int，正态性检验样本量阈值（传递给 check_normality）

    返回：
        dict，包含以下键：
            'descriptive' : DataFrame，各组描述统计
            'normality' : dict，每组正态性检验结果（来自 check_normality）
            'parametric' : bool，是否使用参数检验（全部组正态）
            'test' : dict，包含检验方法、统计量、p值、是否显著
            'effect_size' : dict，效应量名称和值
            'posthoc' : DataFrame 或 None，事后检验结果（若主效应显著）
    作者：Sissi
    """
    # 提取各组数据
    groups = {name: group[value_col].dropna().values for name, group in df.groupby(group_col)}
    group_names = list(groups.keys())

    # 描述统计
    desc = df.groupby(group_col)[value_col].agg(['count', 'mean', 'std', 'median']).round(2)

    # 1. 每组正态性检验（调用 check_normality，关闭绘图）
    normality_results = {}
    all_normal = True
    for name, data in groups.items():
        norm_res = check_normality(
            data,
            sample_size_threshold=normality_threshold,
            method='auto',
            alpha=alpha,
            plot=False          # 不在 compare_groups 中绘图
        )
        normality_results[name] = norm_res
        if not norm_res['is_normal']:
            all_normal = False

    # 2. 根据正态性选择检验
    result = {
        'descriptive': desc,
        'normality': normality_results,
        'parametric': all_normal
    }

    if all_normal:
        # 参数检验分支
        data_groups = [groups[name] for name in group_names]
        # 方差齐性检验
        stat_levene, p_levene = levene(*data_groups)
        homoscedastic = p_levene >= alpha

        if homoscedastic:
            # 单因素 ANOVA
            if pg is not None:
                aov = pg.anova(data=df, dv=value_col, between=group_col, detailed=True)
                f_stat = aov['F'][0]
                p_val = aov['p-unc'][0]
                eta_sq = aov['np2'][0] if 'np2' in aov.columns else None
                method = "ANOVA"
            else:
                model = sm.formula.ols(f'{value_col} ~ C({group_col})', data=df).fit()
                aov_table = sm.stats.anova_lm(model, typ=2)
                f_stat = aov_table['F'][0]
                p_val = aov_table['PR(>F)'][0]
                ss_between = aov_table['sum_sq'][0]
                ss_total = ss_between + aov_table['sum_sq'][1]
                eta_sq = ss_between / ss_total
                method = "ANOVA (statsmodels)"

            # 事后检验：Tukey HSD
            if p_val < alpha:
                tukey = pairwise_tukeyhsd(df[value_col], df[group_col], alpha=alpha)
                posthoc_df = pd.DataFrame(data=tukey.summary().data[1:], columns=tukey.summary().data[0])
            else:
                posthoc_df = None

            effect_size = {'eta_squared': eta_sq}
        else:
            # 方差不齐：Welch ANOVA
            if pg is None:
                raise ImportError("pingouin 是进行 Welch ANOVA 所必需的")
            welch = pg.welch_anova(data=df, dv=value_col, between=group_col)
            f_stat = welch['F'][0]
            p_val = welch['p-unc'][0]
            eta_sq = welch['np2'][0] if 'np2' in welch.columns else None
            method = "Welch ANOVA"

            if p_val < alpha:
                posthoc_df = pg.pairwise_gameshowell(data=df, dv=value_col, between=group_col)
            else:
                posthoc_df = None

            effect_size = {'eta_squared': eta_sq}

        result['test'] = {
            'method': method,
            'statistic': f_stat,
            'p_value': p_val,
            'significant': p_val < alpha
        }
        result['effect_size'] = effect_size
        result['posthoc'] = posthoc_df

    else:
        # 非参数检验分支：Kruskal-Wallis
        data_groups = [groups[name] for name in group_names]
        h_stat, p_val = kruskal(*data_groups)
        method = "Kruskal-Wallis"

        # 效应量 epsilon-squared
        n_total = sum(len(g) for g in data_groups)
        k = len(group_names)
        epsilon_sq = (h_stat - k + 1) / (n_total - k) if n_total > k else np.nan

        if p_val < alpha and sp is not None:
            posthoc_df = sp.posthoc_dunn(df, val_col=value_col, group_col=group_col, p_adjust='bonferroni')
        else:
            posthoc_df = None
            if p_val < alpha and sp is None:
                warnings.warn("主效应显著但无法进行 Dunn 事后检验（需安装 scikit-posthocs）")

        result['test'] = {
            'method': method,
            'statistic': h_stat,
            'p_value': p_val,
            'significant': p_val < alpha
        }
        result['effect_size'] = {'epsilon_squared': epsilon_sq}
        result['posthoc'] = posthoc_df

    return result


def generate_pdf_report(df, group_col, value_col, output_file='report.pdf',
                        alpha=0.05, normality_threshold=5000,
                        figsize_text=(8.5, 11), figsize_plot=(8, 6),
                        chinese_font=None, **plot_kwargs):
    """
    生成单因素分析报告PDF，包含正态性检验、组间比较结果、箱线图和结论。
    自动处理中文显示问题。

    参数：
        df : DataFrame，包含分组列和数值列
        group_col : str，分组列名
        value_col : str，数值列名
        output_file : str，输出PDF文件名
        alpha : float，显著性水平
        normality_threshold : int，正态性检验样本量阈值
        figsize_text : tuple，文本页面的尺寸
        figsize_plot : tuple，箱线图尺寸
        chinese_font : str or None，指定中文字体名称（如'SimHei'），若None则自动尝试常见字体
        **plot_kwargs : 传递给 plot_group_comparison 的其他参数
    
    作者：Sissi
    """
    # 设置中文字体 -------------------------------------------------
    if chinese_font is not None:
        font_list = [chinese_font]
    else:
        # 常见中文字体候选列表（按优先级排序）
        font_list = ['SimHei', 'Microsoft YaHei', 'KaiTi', 'FangSong', 'STSong', 'Arial Unicode MS']

    # 获取系统可用字体名称
    available_fonts = [f.name for f in fm.fontManager.ttflist]
    chosen_font = None
    for font in font_list:
        if font in available_fonts:
            chosen_font = font
            break

    if chosen_font is None:
        warnings.warn("未找到支持中文的字体，PDF中中文可能显示为方框。请手动指定chinese_font参数。")
    else:
        plt.rcParams['font.sans-serif'] = [chosen_font] + plt.rcParams.get('font.sans-serif', [])
        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

    # 调用单因素分析函数 ---------------------------------------------
    res = compare_groups_single_factor(df, group_col, value_col,
                                       alpha=alpha,
                                       normality_threshold=normality_threshold)

    # 构建报告文本内容 -----------------------------------------------
    total_n = len(df)
    n_groups = len(res['descriptive'])

    lines = []
    lines.append("单因素分析报告")
    lines.append("=" * 50)
    lines.append(f"分析变量: {value_col}")
    lines.append(f"分组变量: {group_col}")
    lines.append(f"总样本量: {total_n}, 组数: {n_groups}")
    lines.append("")

    # 描述统计
    lines.append("描述统计:")
    desc_str = res['descriptive'].to_string()
    lines.extend(desc_str.split('\n'))
    lines.append("")

    # 正态性检验结果
    lines.append("正态性检验 (每组):")
    for grp, norm_res in res['normality'].items():
        method = norm_res.get('method', 'N/A')
        p = norm_res.get('p_value', np.nan)
        is_normal = norm_res.get('is_normal', False)
        status = "是" if is_normal else "否"
        lines.append(f"  {grp}: {method} p={p:.4f}, 正态? {status}")
    lines.append("")

    # 主检验结果
    test = res['test']
    lines.append("组间比较结果:")
    lines.append(f"  检验方法: {test['method']}")
    lines.append(f"  统计量: {test['statistic']:.4f}")
    lines.append(f"  p值: {test['p_value']:.4f}")
    lines.append(f"  显著? {'是' if test['significant'] else '否'}")
    if 'effect_size' in res and res['effect_size']:
        es = res['effect_size']
        for k, v in es.items():
            lines.append(f"  效应量 ({k}): {v:.4f}")
    lines.append("")

    # 事后检验摘要
    if test['significant'] and res['posthoc'] is not None:
        posthoc = res['posthoc']
        lines.append("事后检验 (显著差异对):")
        if isinstance(posthoc, pd.DataFrame):
            # 识别常见事后检验格式
            if all(col in posthoc.columns for col in ['group1', 'group2', 'p-adj']):
                sig_pairs = posthoc[posthoc['p-adj'] < alpha]
                for _, row in sig_pairs.iterrows():
                    lines.append(f"  {row['group1']} vs {row['group2']}: p={row['p-adj']:.4f}")
            elif posthoc.index.name is None and posthoc.columns.name is None:
                groups = posthoc.index.tolist()
                for i, g1 in enumerate(groups):
                    for j, g2 in enumerate(groups):
                        if i < j and posthoc.loc[g1, g2] < alpha:
                            lines.append(f"  {g1} vs {g2}: p={posthoc.loc[g1, g2]:.4f}")
            else:
                lines.append("  事后检验结果:")
                lines.extend(posthoc.to_string().split('\n'))
        else:
            lines.append("  事后检验结果: 无法解析")
    else:
        lines.append("事后检验: 不适用")

    # 结论
    lines.append("")
    lines.append("结论:")
    if test['significant']:
        lines.append(f"  不同{group_col}下的{value_col}存在显著差异 ({test['method']}, p={test['p_value']:.4f})。")
        if res['posthoc'] is not None:
            lines.append("  事后检验显示部分组间差异显著（详见上表）。")
    else:
        lines.append(f"  不同{group_col}下的{value_col}无显著差异 (p={test['p_value']:.4f})。")

    # 生成箱线图 ----------------------------------------------------
    from ..visualization import plot_group_comparison
    fig_plot, ax_plot = plot_group_comparison(df, group_col, value_col, res,
                                               figsize=figsize_plot, **plot_kwargs)

    # 保存PDF -------------------------------------------------------
    with PdfPages(output_file) as pdf:
        # 第一页：文本报告
        fig_text = plt.figure(figsize=figsize_text)
        plt.axis('off')
        y_pos = 0.98
        line_height = 0.03
        for line in lines:
            plt.text(0.05, y_pos, line, transform=fig_text.transFigure,
                     fontsize=10, verticalalignment='top', family='sans-serif')
            y_pos -= line_height
        plt.tight_layout()
        pdf.savefig(fig_text)
        plt.close(fig_text)

        # 第二页：箱线图
        pdf.savefig(fig_plot)
        plt.close(fig_plot)

    print(f"报告已保存至: {output_file}")
