"""
BEPCD模型性能分析脚本
用于评估BEPCD模型的MAC值（模型平均复杂度）、单条数据识别时间和参数量
"""

import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pickle
import os
import sys
from sklearn.ensemble import RandomForestClassifier, VotingClassifier
import xgboost as xgb
import catboost as cbt
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from joblib import dump, load
from tqdm import tqdm
import json

# 定义流式处理对象，用于逐一读取数据（模拟实时流分析）
class DataStream:
    def __init__(self):
        pass
        
    def iter_pandas(self, X, y=None):
        """
        迭代处理pandas DataFrame/Series数据
        
        参数:
        X: pandas DataFrame, 特征数据
        y: pandas Series, 标签数据 (可选)
        
        返回:
        逐行迭代X和y的生成器
        """
        for i, xi in X.iterrows():
            if y is not None:
                yi = y.iloc[i] if hasattr(y, 'iloc') else y[i]
                yield xi, yi
            else:
                yield xi

stream = DataStream()

# 模拟BEPCD函数，用于性能分析
def BEPCD(X_test, y_test, m1, m2, model=None):
    """
    BEPCD模型预测函数
    
    参数:
    X_test: 测试特征数据
    y_test: 测试标签数据
    m1: Blending模型
    m2: Stacking模型
    model: 每个类别的最佳模型列表
    
    返回:
    yt: 真实标签列表
    yp: 预测标签列表
    """
    yt = []
    yp = []
    
    # 如果没有提供model参数，则构建一个默认的
    if model is None:
        # 假定所有类别都是用blending_clf
        n_classes = len(np.unique(y_test))
        model = [m1] * n_classes
    
    # 对每个测试样本进行预测
    for xi, yi in stream.iter_pandas(X_test, y_test):
        xi2 = np.array(list(xi.values()))
        xi2 = xi2.reshape(1, -1)
        
        # 获取两个模型的预测
        y_pred1 = m1.predict(xi2)
        y_pred1 = int(y_pred1[0])
        y_pred2 = m2.predict(xi2)
        y_pred2 = int(y_pred2[0])
        
        p1 = m1.predict_proba(xi2)
        p2 = m2.predict_proba(xi2)
        
        y_pred_p1 = np.max(p1)
        y_pred_p2 = np.max(p2)
        
        # BEPCD决策逻辑
        if y_pred1 == y_pred2:
            y_pred = y_pred1
        else:
            # 检查每个预测模型预测的类别是否由对应的模型作为最佳模型
            if model[y_pred1] == m1 and model[y_pred2] != m2:
                y_pred = y_pred1
            elif model[y_pred2] == m2 and model[y_pred1] != m1:
                y_pred = y_pred2
            else:
                # 使用具有更高置信度的模型的预测
                if y_pred_p1 > y_pred_p2:
                    y_pred = y_pred1
                else:
                    y_pred = y_pred2
        
        yt.append(yi)
        yp.append(y_pred)
    
    return yt, yp

def load_models(model_dir="./models"):
    """
    加载保存的模型
    
    参数:
    model_dir: 模型保存的目录路径
    
    返回:
    blending_clf: Blending模型
    stacking_clf: Stacking模型
    """
    try:
        # 尝试从文件加载模型
        blending_clf = load(os.path.join(model_dir, 'blending_model.joblib'))
        stacking_clf = load(os.path.join(model_dir, 'stacking_model.joblib'))
        print("成功加载模型文件")
        return blending_clf, stacking_clf
    except:
        # 如果模型文件不存在，则返回None
        print("模型文件不存在或无法加载")
        return None, None

def load_data(data_path="./data/CHDC_FeatureSelect.csv"):
    """
    加载测试数据集
    
    参数:
    data_path: 数据集路径
    
    返回:
    X_test: 测试特征数据
    y_test: 测试标签数据
    """
    try:
        # 尝试从CSV文件加载数据
        df = pd.read_csv(data_path)
        # 分离特征和标签
        X = df.iloc[:, :-1]  # 除最后一列外的所有列作为特征
        y = df.iloc[:, -1]   # 最后一列作为标签
        
        # 模拟测试集分割
        n_samples = len(df)
        test_size = int(n_samples * 0.2)
        X_test = X.iloc[-test_size:]
        y_test = y.iloc[-test_size:]
        
        print(f"成功加载数据: {len(X_test)}行, {X_test.shape[1]}列")
        return X_test, y_test
    except Exception as e:
        print(f"加载数据失败: {str(e)}")
        return None, None

def count_model_parameters(model):
    """
    计算模型参数量
    
    参数:
    model: 机器学习模型对象
    
    返回:
    params_count: 参数总数
    """
    params_count = 0
    
    # 对于投票分类器，计算每个基模型的参数
    if isinstance(model, VotingClassifier):
        for _, estimator in model.estimators_:
            params_count += count_model_parameters(estimator)
        return params_count
    
    # 随机森林
    if isinstance(model, RandomForestClassifier):
        n_trees = len(model.estimators_)
        for tree in model.estimators_:
            n_nodes = tree.tree_.node_count
            params_count += n_nodes * 3  # 每个节点需要特征索引、阈值和子节点指针
        return params_count
    
    # XGBoost
    if isinstance(model, xgb.XGBClassifier):
        booster = model.get_booster()
        n_trees = len(booster.get_dump())
        # 估计每棵树的平均节点数
        avg_nodes_per_tree = 50  # 这是一个估计值
        params_count += n_trees * avg_nodes_per_tree * 3
        return params_count
    
    # CatBoost
    if isinstance(model, cbt.CatBoostClassifier):
        n_trees = model.tree_count_
        # 估计每棵树的平均节点数
        avg_nodes_per_tree = 50  # 这是一个估计值
        params_count += n_trees * avg_nodes_per_tree * 3
        return params_count
    
    # 默认返回特征数乘以一个系数
    return 1000  # 默认估计值

def calculate_MAC(model, X_data):
    """
    计算模型的MAC值（模型平均复杂度）
    
    参数:
    model: 机器学习模型
    X_data: 输入特征数据
    
    返回:
    mac_value: MAC值
    """
    n_features = X_data.shape[1]
    
    # 对于VotingClassifier
    if isinstance(model, VotingClassifier):
        total_ops = 0
        for _, estimator in model.estimators_:
            # 为每个基模型计算操作
            if isinstance(estimator, RandomForestClassifier):
                n_trees = len(estimator.estimators_)
                total_ops += n_features * n_trees  # 每棵树需要评估所有特征
            elif isinstance(estimator, xgb.XGBClassifier):
                n_trees = estimator.get_booster().tree_count
                total_ops += n_features * n_trees
            elif isinstance(estimator, cbt.CatBoostClassifier):
                n_trees = estimator.tree_count_
                total_ops += n_features * n_trees
            else:
                # 其他模型的默认估计
                total_ops += n_features * 10
                
        return total_ops
    
    # 对于随机森林
    if isinstance(model, RandomForestClassifier):
        n_trees = len(model.estimators_)
        return n_features * n_trees
    
    # 其他模型的默认估计
    return n_features * 10

def analyze_bepcd_performance(X_test, y_test, blending_clf, stacking_clf, sample_size=1000):
    """
    分析BEPCD模型性能
    
    参数:
    X_test: 测试特征数据
    y_test: 测试标签数据
    blending_clf: Blending模型
    stacking_clf: Stacking模型
    sample_size: 用于分析的样本数量
    
    返回:
    results: 性能分析结果字典
    """
    # 初始化结果字典
    results = {
        'mac_value': {},
        'processing_time': {},
        'parameters_count': {},
        'accuracy': {}
    }
    
    # 对X_test进行采样
    if len(X_test) > sample_size:
        indices = np.random.choice(len(X_test), sample_size, replace=False)
        X_sample = X_test.iloc[indices]
        y_sample = y_test.iloc[indices]
    else:
        X_sample = X_test
        y_sample = y_test
    
    # 1. 计算MAC值
    print("1. 计算模型MAC值...")
    mac_blending = calculate_MAC(blending_clf, X_sample)
    mac_stacking = calculate_MAC(stacking_clf, X_sample)
    mac_bepcd = mac_blending + mac_stacking + 10  # 额外的决策复杂度
    
    results['mac_value']['blending'] = mac_blending
    results['mac_value']['stacking'] = mac_stacking
    results['mac_value']['bepcd'] = mac_bepcd
    
    # 2. 计算模型参数量
    print("2. 计算模型参数量...")
    params_blending = count_model_parameters(blending_clf)
    params_stacking = count_model_parameters(stacking_clf)
    params_bepcd = params_blending + params_stacking
    
    results['parameters_count']['blending'] = params_blending
    results['parameters_count']['stacking'] = params_stacking
    results['parameters_count']['bepcd'] = params_bepcd
    
    # 3. 计算单条数据的处理时间
    print("3. 计算单条数据处理时间...")
    processing_times = []
    
    for i in tqdm(range(min(100, len(X_sample)))):
        xi = X_sample.iloc[i]
        xi_values = np.array(list(xi.values()))
        xi_reshaped = xi_values.reshape(1, -1)
        
        # 测量BEPCD单条数据处理时间
        start_time = time.perf_counter()
        
        # BEPCD预测过程
        y_pred1 = blending_clf.predict(xi_reshaped)
        y_pred2 = stacking_clf.predict(xi_reshaped)
        p1 = blending_clf.predict_proba(xi_reshaped)
        p2 = stacking_clf.predict_proba(xi_reshaped)
        
        # 简化版决策逻辑
        if y_pred1[0] == y_pred2[0]:
            y_pred = y_pred1[0]
        else:
            if np.max(p1) > np.max(p2):
                y_pred = y_pred1[0]
            else:
                y_pred = y_pred2[0]
        
        end_time = time.perf_counter()
        processing_time = (end_time - start_time) * 1000  # 转换为毫秒
        processing_times.append(processing_time)
    
    # 计算处理时间统计
    avg_time = np.mean(processing_times)
    std_time = np.std(processing_times)
    min_time = np.min(processing_times)
    max_time = np.max(processing_times)
    p95_time = np.percentile(processing_times, 95)
    
    results['processing_time']['average_ms'] = avg_time
    results['processing_time']['std_ms'] = std_time
    results['processing_time']['min_ms'] = min_time
    results['processing_time']['max_ms'] = max_time
    results['processing_time']['p95_ms'] = p95_time
    results['processing_time']['samples'] = processing_times
    
    # 4. 计算模型准确性
    print("4. 计算模型准确性...")
    yt, yp = BEPCD(X_sample, y_sample, blending_clf, stacking_clf)
    
    accuracy = accuracy_score(yt, yp)
    precision = precision_score(yt, yp, average='weighted')
    recall = recall_score(yt, yp, average='weighted')
    f1 = f1_score(yt, yp, average='weighted')
    
    results['accuracy']['accuracy'] = accuracy
    results['accuracy']['precision'] = precision
    results['accuracy']['recall'] = recall 
    results['accuracy']['f1'] = f1
    
    return results

def visualize_results(results):
    """
    可视化分析结果
    
    参数:
    results: 性能分析结果字典
    """
    plt.figure(figsize=(18, 12))
    
    # 1. MAC值比较
    plt.subplot(2, 2, 1)
    mac_data = [results['mac_value']['blending'], 
                results['mac_value']['stacking'], 
                results['mac_value']['bepcd']]
    plt.bar(['Blending', 'Stacking', 'BEPCD'], mac_data)
    plt.title('Model Average Complexity (MAC)')
    plt.ylabel('MAC Value (operations)')
    plt.grid(axis='y', linestyle='--', alpha=0.7)
    
    # 2. 参数量比较
    plt.subplot(2, 2, 2)
    params_data = [results['parameters_count']['blending'], 
                   results['parameters_count']['stacking'], 
                   results['parameters_count']['bepcd']]
    plt.bar(['Blending', 'Stacking', 'BEPCD'], params_data)
    plt.title('Model Parameters Count')
    plt.ylabel('Parameters Count')
    plt.grid(axis='y', linestyle='--', alpha=0.7)
    
    # 3. 处理时间分布
    plt.subplot(2, 2, 3)
    times = results['processing_time']['samples']
    plt.hist(times, bins=30, alpha=0.7)
    plt.axvline(results['processing_time']['average_ms'], color='r', 
                linestyle='--', label=f'Mean: {results["processing_time"]["average_ms"]:.2f} ms')
    plt.axvline(results['processing_time']['p95_ms'], color='g', 
                linestyle='--', label=f'95th Percentile: {results["processing_time"]["p95_ms"]:.2f} ms')
    plt.title('Single Data Processing Time Distribution')
    plt.xlabel('Time (ms)')
    plt.ylabel('Frequency')
    plt.legend()
    plt.grid(True, alpha=0.3)
    
    # 4. 准确性指标
    plt.subplot(2, 2, 4)
    metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
    values = [results['accuracy']['accuracy'], 
              results['accuracy']['precision'], 
              results['accuracy']['recall'], 
              results['accuracy']['f1']]
    plt.bar(metrics, values)
    plt.title('BEPCD Model Performance Metrics')
    plt.ylabel('Score')
    plt.ylim(0, 1)
    plt.grid(axis='y', linestyle='--', alpha=0.7)
    
    plt.tight_layout()
    plt.savefig('bepcd_performance_analysis.png', dpi=300)
    plt.show()

def print_summary(results):
    """
    打印结果摘要
    
    参数:
    results: 性能分析结果字典
    """
    print("\n" + "="*80)
    print(" "*30 + "BEPCD模型性能分析摘要")
    print("="*80)
    
    print("\n1. MAC值分析 (模型平均复杂度)")
    print("-"*80)
    print(f"Blending模型 MAC值: {results['mac_value']['blending']:,} 操作")
    print(f"Stacking模型 MAC值: {results['mac_value']['stacking']:,} 操作")
    print(f"BEPCD模型 总MAC值: {results['mac_value']['bepcd']:,} 操作")
    
    print("\n2. 模型参数量分析")
    print("-"*80)
    print(f"Blending模型参数量: {results['parameters_count']['blending']:,} 参数")
    print(f"Stacking模型参数量: {results['parameters_count']['stacking']:,} 参数")
    print(f"BEPCD模型总参数量: {results['parameters_count']['bepcd']:,} 参数")
    
    print("\n3. 单条数据处理时间分析")
    print("-"*80)
    print(f"平均处理时间: {results['processing_time']['average_ms']:.3f} 毫秒/样本")
    print(f"处理时间标准差: {results['processing_time']['std_ms']:.3f} 毫秒")
    print(f"最小处理时间: {results['processing_time']['min_ms']:.3f} 毫秒")
    print(f"最大处理时间: {results['processing_time']['max_ms']:.3f} 毫秒")
    print(f"95%分位处理时间: {results['processing_time']['p95_ms']:.3f} 毫秒")
    
    print("\n4. 模型准确性指标")
    print("-"*80)
    print(f"准确率: {results['accuracy']['accuracy']:.4f}")
    print(f"精确率: {results['accuracy']['precision']:.4f}")
    print(f"召回率: {results['accuracy']['recall']:.4f}")
    print(f"F1分数: {results['accuracy']['f1']:.4f}")
    
    print("\n5. 性能总结")
    print("-"*80)
    avg_time_sec = results['processing_time']['average_ms'] / 1000
    samples_per_second = 1 / avg_time_sec
    print(f"BEPCD理论处理速度: {samples_per_second:.2f} 样本/秒")
    print(f"处理1000条数据估计需要: {1000/samples_per_second:.2f} 秒")
    
    mac_per_time = results['mac_value']['bepcd'] / avg_time_sec
    print(f"计算效率: {mac_per_time/1e9:.4f} GOPS (10亿操作/秒)")
    
    print("="*80)

def main():
    """
    主函数
    """
    print("="*50)
    print(" "*10 + "BEPCD模型性能分析")
    print("="*50)
    
    # 1. 加载模型
    print("\n1. 加载模型...")
    blending_clf, stacking_clf = load_models()
    
    # 如果模型加载失败，尝试创建模型
    if blending_clf is None or stacking_clf is None:
        print("\n模型加载失败，尝试创建模型...")
        # 此处需要添加模型创建代码
        print("此脚本需要预先训练好的模型。请先运行IDS.ipynb训练模型并保存。")
        return
    
    # 2. 加载数据
    print("\n2. 加载测试数据...")
    X_test, y_test = load_data()
    
    if X_test is None or y_test is None:
        print("数据加载失败，退出程序。")
        return
    
    # 3. 分析模型性能
    print("\n3. 分析BEPCD模型性能...")
    results = analyze_bepcd_performance(X_test, y_test, blending_clf, stacking_clf)
    
    # 4. 打印结果摘要
    print_summary(results)
    
    # 5. 可视化结果
    print("\n5. 生成可视化结果...")
    visualize_results(results)
    
    # 6. 保存结果
    print("\n6. 保存分析结果...")
    with open('bepcd_performance_results.json', 'w') as f:
        # 将numpy数组转换为列表以便JSON序列化
        serializable_results = results.copy()
        serializable_results['processing_time']['samples'] = [float(x) for x in results['processing_time']['samples']]
        json.dump(serializable_results, f, indent=4)
    
    print(f"\n分析完成！结果已保存到 'bepcd_performance_results.json' 和 'bepcd_performance_analysis.png'")

if __name__ == "__main__":
    main() 