import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import uuid
import logging
import json
import traceback
from datetime import datetime, timedelta
from scipy import stats
from scipy.stats import entropy
from scipy.signal import welch, find_peaks
from fastapi import FastAPI, HTTPException, Body
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from typing import List, Dict, Any, Optional
from td_connector import connect_to_tdengine, execute_query
from td_config import APP_CONFIG, API_CONFIG, get_image_url, validate_config

import matplotlib
matplotlib.use('Agg')
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# 验证配置
validate_config()

# 使用配置的图片目录
IMAGES_DIR = APP_CONFIG['images_dir']
os.makedirs(IMAGES_DIR, exist_ok=True)

app = FastAPI(
    title=APP_CONFIG['title'],
    description=APP_CONFIG['description'],
    version=APP_CONFIG['version']
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/static", StaticFiles(directory="src/td_analysis/static"), name="static")


class PrefixRequest(BaseModel):
    prefix: str

class TimestampAnalysisRequest(BaseModel):
    table_name: str
    timestamp: str

class SidebandRequest(BaseModel):
    table_name: str

# 添加MutationAnalysis模型类
class MutationAnalysis(BaseModel):
    first_sample_concentration: float
    first_sample_time: datetime
    second_sample_concentration: float
    second_sample_time: datetime

class AnalysisResponse(BaseModel):
    metrics: Dict[str, float]
    scores: Dict[str, float]
    total_score: float
    status: str
    time_series_plot_url: str
    fft_plot_url: str
    statistical_metrics: Dict[str, Any]

class SidebandEnergyResponse(BaseModel):
    sideband_energy_plot_url: str
    sideband_energy_data: List[Dict[str, Any]]
    warning_threshold: float
    alert_threshold: float
    warning_count: int
    alert_count: int

def get_tables_by_prefix(prefix):
    """
    获取以特定前缀开头的所有表名

    """
    try:
        conn = connect_to_tdengine()
        tables_df = execute_query(conn, "SHOW TABLES")
        # 不需要关闭 REST API 连接
        
        if not tables_df.empty:
            # 打印列名以便调试
            logger.info(f"SHOW TABLES 返回的列名: {tables_df.columns.tolist()}")
            
            # 尝试找到表名列 - 可能是 'name', 'table_name' 或其他
            table_name_col = None
            for possible_col in ['table_name', 'name', 'tablename', 'tbname']:
                if possible_col in tables_df.columns:
                    table_name_col = possible_col
                    logger.info(f"找到表名列: {table_name_col}")
                    break
            
            if table_name_col:
                matching_tables = [table for table in tables_df[table_name_col].tolist() 
                                  if table.startswith(prefix)]
                return matching_tables
            else:
                logger.error(f"在返回的数据中未找到表名列。列名: {tables_df.columns.tolist()}")
                # 如果找不到表名列，尝试使用第一列作为表名
                if len(tables_df.columns) > 0:
                    first_col = tables_df.columns[0]
                    logger.info(f"使用第一列 '{first_col}' 作为表名列")
                    matching_tables = [table for table in tables_df[first_col].tolist() 
                                     if table.startswith(prefix)]
                    return matching_tables
                return []
        return []
    except Exception as e:
        logger.error(f"获取表列表时出错: {str(e)}")
        return []

def get_timestamps_from_table(table_name):
    """
    获取指定表中的时间戳列表，只取整秒的时间戳

    """
    try:
        conn = connect_to_tdengine()

        query = f"SELECT ts FROM {table_name} WHERE (CAST(ts AS BIGINT) % 1000000000) = 0"
        logger.info(f"执行查询: {query}")
        df = execute_query(conn, query)
        print(df)
        timestamps = []
        
        # 打印列名以便调试
        logger.info(f"时间戳查询返回的列名: {df.columns.tolist()}")
        
        if not df.empty:
            # 确定时间戳列名
            ts_col = None
            if 'ts' in df.columns:
                ts_col = 'ts'
            elif 0 in df.columns:
                ts_col = 0
            elif '0' in df.columns:
                ts_col = '0'
            elif len(df.columns) > 0:  # 如果没有找到预期的列名，使用第一列
                ts_col = df.columns[0]
                
            logger.info(f"使用列 '{ts_col}' 作为时间戳列")
            
            if ts_col is not None:
                for ts in df[ts_col].tolist():
                    if hasattr(ts, 'isoformat'):
                        formatted_ts = ts.strftime('%Y-%m-%d %H:%M:%S')
                        timestamps.append(formatted_ts)
                    elif isinstance(ts, (int, float)) or (isinstance(ts, str) and ts.isdigit()):
                        ts_value = int(ts)
                        ts_seconds = ts_value / 1_000_000_000
                        dt = datetime.fromtimestamp(ts_seconds)
                        formatted_ts = dt.strftime('%Y-%m-%d %H:%M:%S')
                        timestamps.append(formatted_ts)
                    else:
                        try:
                            timestamps.append(str(ts))
                        except:
                            timestamps.append(str(ts))
            
            logger.info(f"从表 {table_name} 中提取了 {len(timestamps)} 个时间戳")
        
        # 不需要关闭 REST API 连接
        return timestamps
    except Exception as e:
        logger.error(f"获取表 {table_name} 的时间戳列表时出错: {str(e)}")
        return []

def get_data_by_timestamp(table_name, timestamp):
    """
    获取指定表在特定时间戳的数据
    
    Args:
        table_name: 表名
        timestamp: 时间戳 (ISO 8601格式)
        
    Returns:
        DataFrame: 包含vals数据的DataFrame
    """
    try:
        conn = connect_to_tdengine()
        
        # 将时间戳转换为datetime对象
        try:
            dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
            # 转换为ISO 8601格式，包含时区信息
            start_ts = dt.strftime('%Y-%m-%dT%H:%M:%S+08:00')
            
            # 计算下一秒的时间戳
            end_dt = dt + timedelta(seconds=1)
            end_ts = end_dt.strftime('%Y-%m-%dT%H:%M:%S+00:00')
        except ValueError:
            logger.warning(f"无法解析时间戳 {timestamp}，尝试直接使用")
            start_ts = timestamp
            try:
                # 尝试解析时间戳并添加一秒
                dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
                end_dt = dt + timedelta(seconds=1)
                end_ts = end_dt.strftime('%Y-%m-%dT%H:%M:%S+00:00')
            except:
                logger.error(f"无法计算下一秒时间戳，使用近似查询")
                end_ts = None
        
        # 构建查询
        if end_ts:
            # 使用精确时间范围查询
            query = f"SELECT vals FROM {table_name} WHERE ts >= '{start_ts}' AND ts < '{end_ts}'"
        else:
            # 如果无法计算结束时间，使用简单查询
            query = f"SELECT vals FROM {table_name} WHERE ts >= '{start_ts}'"
            
        logger.info(f"执行查询: {query}")
        df = execute_query(conn, query)
        
        # 如果没有找到数据，尝试更宽松的匹配
        if df.empty:
            logger.info(f"没有找到基于时间戳 {start_ts} 的数据，尝试查询第一条数据")
            query = f"SELECT vals FROM {table_name} LIMIT 1"
            logger.info(f"执行查询: {query}")
            df = execute_query(conn, query)
        
        # 检查DataFrame中是否有vals列或数字列
        if 'vals' not in df.columns:
            # 检查是否有数字列（可能是0）
            numeric_cols = [col for col in df.columns if isinstance(col, (int, float)) or str(col).isdigit()]
            if numeric_cols:
                # 重命名第一个数字列为vals
                df = df.rename(columns={numeric_cols[0]: 'vals'})
                logger.info(f"将列 {numeric_cols[0]} 重命名为vals")
            else:
                logger.warning(f"DataFrame中没有vals列或数字列")
                logger.warning(f"可用的列: {df.columns.tolist()}")
                return pd.DataFrame()
        
        return df
        
    except Exception as e:
        logger.error(f"获取数据时出错: {str(e)}")
        raise

def extract_vals_data(df):
    """
    从DataFrame中提取vals列的数据
    """
    try:
        if df.empty:
            logger.warning("DataFrame为空，无法提取vals数据")
            return np.array([])
        
        # 检查是否有vals列或数字列
        if 'vals' not in df.columns:
            numeric_cols = [col for col in df.columns if isinstance(col, (int, float)) or str(col).isdigit()]
            if numeric_cols:
                # 使用第一个数字列
                vals_data = df[numeric_cols[0]].values
            else:
                logger.warning("DataFrame中没有vals列或数字列")
                return np.array([])
        else:
            vals_data = df['vals'].values
        
        # 确保数据是数值类型
        vals_data = np.array(vals_data, dtype=float)
        
        logger.info(f"成功从DataFrame中提取了 {len(vals_data)} 个数据点")
        return vals_data
    except Exception as e:
        logger.error(f"提取vals数据时出错: {str(e)}")
        return np.array([])

def save_time_series_plot(data_values, table_name):
    """创建时序数据图并保存为文件，返回访问URL"""
    plt.figure(figsize=(12, 6))
    
    # 创建时间轴（假设等时间间隔）
    x = np.arange(len(data_values))
    
    plt.plot(x, data_values)
    plt.title(f"Time Series Data - Table: {table_name}")
    plt.xlabel("Sample Index")
    plt.ylabel("Amplitude")
    plt.grid(True)
    
    # 生成唯一文件名并保存
    file_name = f"ts_{table_name}_{uuid.uuid4().hex[:8]}.png"
    file_path = os.path.join(IMAGES_DIR, file_name)
    plt.savefig(file_path, format='png')
    plt.close()
    
    # 返回文件URL (使用配置的基础URL)
    return get_image_url(file_name)

def save_fft_plot(data_values, table_name):
    """创建FFT频谱图并保存为文件，返回访问URL"""
    plt.figure(figsize=(12, 6))
    
    # 执行FFT
    fft_values = np.fft.fft(data_values)
    n = len(data_values)
    frequency = np.fft.fftfreq(n)[:n//2]
    amplitude = np.abs(fft_values)[:n//2]
    
    plt.plot(frequency, amplitude)
    plt.title(f"FFT Spectrum - Table: {table_name}")
    plt.xlabel("Frequency")
    plt.ylabel("Amplitude")
    plt.grid(True)

    file_name = f"fft_{table_name}_{uuid.uuid4().hex[:8]}.png"
    file_path = os.path.join(IMAGES_DIR, file_name)
    plt.savefig(file_path, format='png')
    plt.close()

    return get_image_url(file_name)

def calculate_statistical_metrics(data_values):
    """计算时序数据的统计指标"""
    data_array = np.array(data_values)
    
    metrics = {
        "mean": float(np.mean(data_array)),
        "median": float(np.median(data_array)),
        "std_dev": float(np.std(data_array)),
        "min": float(np.min(data_array)),
        "max": float(np.max(data_array)),
        "samples": len(data_array),
    }
    

    fs = 1.0
    f, Pxx = welch(data_array, fs, nperseg=min(256, len(data_array)))

    dominant_indices = np.argsort(Pxx)[-5:][::-1]
    dominant_freqs = [{
        "frequency": float(f[idx]),
        "magnitude": float(Pxx[idx])
    } for idx in dominant_indices]
    
    metrics["dominant_frequencies"] = dominant_freqs
    
    return metrics

def calculate_vibration_power_entropy(data_values):
    """
    计算振动功率熵 (0-2.8)
    """
    try:
        # 计算FFT
        fft_values = np.fft.fft(data_values)
        # 取绝对值得到幅度谱
        amplitude_spectrum = np.abs(fft_values)
        
        # 计算功率谱
        power_spectrum = amplitude_spectrum ** 2
        
        # 归一化功率谱（使总和为1）
        total_power = np.sum(power_spectrum)
        if total_power > 0:
            normalized_power = power_spectrum / total_power
        else:
            return 0.0
        
        # 计算Shannon熵
        vibration_entropy = -np.sum(normalized_power * np.log2(normalized_power + 1e-10))
        
        return min(max(vibration_entropy, 0.0), 2.8)  # 限制在0-2.8范围内
    except Exception as e:
        logger.error(f"计算振动功率熵时出错: {str(e)}")
        return 0.0

def calculate_vibration_determinacy(data_values):
    """
    计算振动确定度 (0.9-0)
    """
    try:
        n = len(data_values)
        if n < 2:
            return 0.9
        
        # 计算自相关函数
        autocorr = np.correlate(data_values, data_values, mode='full')
        if autocorr[n-1] == 0:
            return 0.0
            
        autocorr = autocorr[n-1:] / autocorr[n-1]  # 标准化
        
        # 计算自相关函数的衰减率
        decay_rate = 0.0
        for i in range(1, min(20, len(autocorr))):
            decay_rate += abs(autocorr[i])
        
        decay_rate = decay_rate / min(19, len(autocorr) - 1)
        
        # 转换为确定度（衰减率越大，确定度越低）
        determinacy = 0.9 * (1 - decay_rate)
        
        return min(max(determinacy, 0.0), 0.9)  # 限制在0-0.9范围内
    except Exception as e:
        logger.error(f"计算振动确定度时出错: {str(e)}")
        return 0.0

def calculate_vibration_fluctuation_rate(data_values):
    """
    计算振动波动率 (0%-20%)
    """
    try:
        if len(data_values) < 2:
            return 0.0
        
        # 计算相邻样本之间的差异
        diffs = np.abs(np.diff(data_values))
        
        # 计算均值
        mean_value = np.mean(np.abs(data_values))
        
        # 计算波动率
        if mean_value > 0:
            fluctuation_rate = 100 * np.mean(diffs) / mean_value
        else:
            fluctuation_rate = 0.0
        
        return min(max(fluctuation_rate, 0.0), 20.0)  # 限制在0-20%范围内
    except Exception as e:
        logger.error(f"计算振动波动率时出错: {str(e)}")
        return 0.0

def calculate_high_frequency_vibration_ratio(data_values):
    """
    计算高频振动占比 (0%-35%)
    """
    try:
        # 计算FFT
        fft_values = np.fft.fft(data_values)
        amplitude = np.abs(fft_values)
        
        # 计算总能量
        total_energy = np.sum(amplitude ** 2)
        
        if total_energy == 0:
            return 0.0
        
        # 定义高频部分（频率大于采样频率的1/3）
        high_freq_idx = len(amplitude) // 3
        high_freq_energy = np.sum(amplitude[high_freq_idx:] ** 2)
        
        # 计算高频占比
        high_freq_ratio = 100 * high_freq_energy / total_energy
        
        return min(max(high_freq_ratio, 0.0), 35.0)  # 限制在0-35%范围内
    except Exception as e:
        logger.error(f"计算高频振动占比时出错: {str(e)}")
        return 0.0

def map_to_score(value, min_val, max_val, distribution_type, reverse=False):
    """
    根据指定的分布类型将指标值映射为分数

    """
    # 标准化到0-1范围
    normalized_value = (value - min_val) / (max_val - min_val)
    normalized_value = max(0, min(normalized_value, 1))
    
    if reverse:
        normalized_value = 1 - normalized_value
    
    # 根据分布类型映射到分数
    if distribution_type == 'normal':
        # 正态分布 (均值0.5, 标准差0.2)
        score = 100 * stats.norm.cdf(normalized_value, loc=0.5, scale=0.2)
    
    elif distribution_type == 'right_skewed':
        # 右偏正态分布 (使用Beta分布)
        score = 100 * stats.beta.cdf(normalized_value, a=2, b=5)
    
    elif distribution_type == 'left_skewed':
        # 左偏正态分布 (使用Beta分布)
        score = 100 * stats.beta.cdf(normalized_value, a=5, b=2)
    
    elif distribution_type == 'poisson':
        # 泊松分布近似 (使用Gamma分布)
        score = 100 * stats.gamma.cdf(normalized_value, a=2)
    
    else:
        # 默认线性映射
        score = 100 * normalized_value
    
    return score

def calculate_vibration_metrics(data_values):
    """计算所有振动指标"""
    metrics = {
        "vibration_power_entropy": calculate_vibration_power_entropy(data_values),
        "vibration_determinacy": calculate_vibration_determinacy(data_values),
        "vibration_fluctuation_rate": calculate_vibration_fluctuation_rate(data_values),
        "high_frequency_vibration_ratio": calculate_high_frequency_vibration_ratio(data_values)
    }
    
    # 计算各指标的分数
    scores = {
        "vibration_power_entropy": map_to_score(
            metrics["vibration_power_entropy"], 0, 2.8, 'normal', reverse=False
        ),
        "vibration_determinacy": map_to_score(
            metrics["vibration_determinacy"], 0, 0.9, 'right_skewed', reverse=True
        ),
        "vibration_fluctuation_rate": map_to_score(
            metrics["vibration_fluctuation_rate"], 0, 20, 'left_skewed', reverse=False
        ),
        "high_frequency_vibration_ratio": map_to_score(
            metrics["high_frequency_vibration_ratio"], 0, 35, 'poisson', reverse=False
        )
    }
    
    # 计算总分（四个指标的平均值）
    total_score = sum(scores.values()) / len(scores)
    
    # 确定状态
    if total_score >= 81:
        status = "异常状态"
    elif total_score >= 69:
        status = "注意状态"
    else:
        status = "正常状态"
    
    return metrics, scores, total_score, status

def analyze_table_data_by_timestamp(table_name, timestamp):
    """
    分析特定表在指定时间戳的数据

    """
    try:
        logger.info(f"开始分析表 '{table_name}' 在时间戳 {timestamp} 的数据")
        
        # 获取指定时间戳的数据
        data_df = get_data_by_timestamp(table_name, timestamp)
        
        if data_df.empty:
            logger.warning(f"表 '{table_name}' 在时间戳 {timestamp} 没有数据")
            return {"error": f"表 '{table_name}' 在时间戳 {timestamp} 没有数据"}
        
        # 提取vals数据
        data_values = extract_vals_data(data_df)
        
        if len(data_values) == 0:
            logger.warning(f"表 '{table_name}' 在时间戳 {timestamp} 没有可用的vals数据")
            return {"error": f"表 '{table_name}' 在时间戳 {timestamp} 没有可用的vals数据"}
        
        # 生成图表
        time_series_plot_url = save_time_series_plot(data_values, table_name)
        fft_plot_url = save_fft_plot(data_values, table_name)
        
        # 计算统计指标
        statistical_metrics = calculate_statistical_metrics(data_values)
        
        # 计算振动指标
        metrics, scores, total_score, status = calculate_vibration_metrics(data_values)
        
        # 记录分析结果
        actual_timestamp = data_df.iloc[0]['ts'].isoformat() if 'ts' in data_df.columns else timestamp
        
        return {
            "table_name": table_name,
            "timestamp": actual_timestamp,
            "metrics": metrics,
            "scores": scores,
            "total_score": total_score,
            "status": status,
            "time_series_plot_url": time_series_plot_url,
            "fft_plot_url": fft_plot_url,
            "statistical_metrics": statistical_metrics
        }
    except Exception as e:
        logger.error(f"分析表 '{table_name}' 在时间戳 {timestamp} 的数据时出错: {str(e)}")
        return {"error": str(e)}

# API路由
@app.get("/")
def read_root():
    """API根路径"""
    return {
        "message": "变压器振动分析API",
        "version": "1.0.0",
        "endpoints": {
            "/tables/prefix": "POST - 获取指定前缀的表列表",
            "/analyze/timestamp": "POST - 分析指定表在特定时间戳的数据",
            "/analyze/sideband-energy": "POST - 分析指定表中所有时间段的边频能量比",
            "/mutationAnalysis": "POST - 基于溶解气体分析突变分析"
        }
    }

@app.post("/tables/prefix")
def get_tables_by_prefix_api(request: PrefixRequest):
    """
    获取指定前缀的表列表和特定表的时间戳

    """
    try:
        tables = get_tables_by_prefix(request.prefix)
        
        if not tables:
            return {
                "status": "success",
                "message": f"没有找到以 '{request.prefix}' 开头的表",
                "tables": [],
                "timestamps": []
            }
        
        # 尝试找到a00001表或使用第一个表
        timestamp_table = None
        if "a00001" in tables:
            timestamp_table = "a00001"
        else:
            timestamp_table = tables[0]
        
        # 获取选定表的时间戳
        timestamps = get_timestamps_from_table(timestamp_table)
        
        return {
            "status": "success",
            "message": f"找到 {len(tables)} 个以 '{request.prefix}' 开头的表",
            "tables": tables,
            "timestamps": timestamps
        }
    except Exception as e:
        logger.error(f"获取表前缀时出错: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/analyze/timestamp", response_model=AnalysisResponse)
def analyze_by_timestamp_api(request: TimestampAnalysisRequest):
    """
    分析指定表在特定时间戳的数据

    """
    try:
        result = analyze_table_data_by_timestamp(request.table_name, request.timestamp)
        
        if "error" in result:
            raise HTTPException(status_code=404, detail=result["error"])
        
        return result
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"分析时间戳数据时出错: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))

def get_all_timeseries_data(table_name):
    """
    获取指定表的所有时序数据，并按秒分组
    """
    try:
        conn = connect_to_tdengine()
        
        # 首先直接获取所有数据，然后在Python中进行分组处理
        # 这样可以避免复杂的TDengine时间戳语法问题
        data_query = f"SELECT ts, vals FROM {table_name} ORDER BY ts"
        logger.info(f"执行查询: {data_query}")
        
        all_data_df = execute_query(conn, data_query)
        if all_data_df.empty:
            logger.warning(f"表 {table_name} 中没有找到数据")
            return []
        
        # 确定列名
        ts_col = 'ts' if 'ts' in all_data_df.columns else all_data_df.columns[0]
        vals_col = 'vals' if 'vals' in all_data_df.columns else all_data_df.columns[1]
        
        logger.info(f"获取到 {len(all_data_df)} 条数据记录，列名: {all_data_df.columns.tolist()}")
        logger.info(f"数据示例: {all_data_df.iloc[0].to_dict()}")
        
        # 确保pandas日期时间格式正确
        try:
            # 尝试将时间戳列转换为pandas datetime
            all_data_df[ts_col] = pd.to_datetime(all_data_df[ts_col])
            logger.info(f"成功将时间戳转换为标准日期时间格式")
        except Exception as e:
            logger.warning(f"时间戳转换失败，将继续使用原始格式: {str(e)}")
        
        # 创建一个新列，只包含秒级时间戳
        try:
            all_data_df['second_ts'] = all_data_df[ts_col].dt.strftime('%Y-%m-%d %H:%M:%S')
        except Exception as e:
            logger.warning(f"创建秒级时间戳失败: {str(e)}")
            # 备选方案：手动处理时间戳
            all_data_df['second_ts'] = all_data_df[ts_col].apply(lambda x: 
                x.strftime('%Y-%m-%d %H:%M:%S') if hasattr(x, 'strftime') else str(x).split('.')[0])
        
        # 按秒级时间戳分组
        time_segments = []
        grouped = all_data_df.groupby('second_ts')
        
        for second_ts, group in grouped:
            # 检查数据点数量
            if len(group) < 100:  # 设置一个最小点数阈值
                logger.warning(f"时间段 {second_ts} 的数据点数量过少 ({len(group)}), 跳过")
                continue
            
            # 将数据添加到结果列表
            time_segments.append({
                'timestamp': second_ts,
                'values': group[vals_col].values,
                'raw_data': group
            })
            logger.info(f"时间段 {second_ts} 包含 {len(group)} 个数据点")
        
        logger.info(f"在表 {table_name} 中找到 {len(time_segments)} 个有效时间段")
        return time_segments
        
    except Exception as e:
        logger.error(f"获取表 {table_name} 的时序数据时出错: {str(e)}")
        raise

def calculate_sideband_energy_ratio(signal, fs=8192, base_freq=50, fb=15, freq_range=(0, 1000), bandwidth=2):
    """
    计算振动信号的边频能量比
    
    参数:
    signal: 振动信号数据
    fs: 采样频率
    base_freq: 基频（Hz）
    fb: 边频间隔（Hz）
    freq_range: 频率范围，格式为(min_freq, max_freq)
    bandwidth: 频带宽度（Hz）
    
    返回:
    边频能量比（%）
    """
    try:
        # 确保数据是numpy数组
        signal = np.array(signal, dtype=float)
        
        # 去除直流分量
        signal = signal - np.mean(signal)
        
        # 计算频谱
        N = len(signal)
        freq = np.fft.fftfreq(N, 1/fs)
        freq_positive = freq[:N//2]
        fft_signal = np.fft.fft(signal)
        magnitude = 2 * np.abs(fft_signal) / N
        magnitude_positive = magnitude[:N//2]
        
        # 限制频率范围
        mask = (freq_positive >= freq_range[0]) & (freq_positive <= freq_range[1])
        freq_limited = freq_positive[mask]
        magnitude_limited = magnitude_positive[mask]
        
        # 设置振幅阈值（用于峰值检测）
        min_amplitude = 0.001 * np.max(magnitude_limited)
        
        # 找出所有主要峰值
        peaks, _ = find_peaks(magnitude_limited, height=min_amplitude)
        peak_freqs = freq_limited[peaks]
        peak_amplitudes = magnitude_limited[peaks]
        
        # 识别基频及其谐波
        center_freqs = []
        tolerance = 2  # 频率匹配容差（Hz）
        
        for multiple in range(1, int(freq_range[1]/base_freq) + 1):
            theoretical_freq = multiple * base_freq
            
            # 跳过超出范围的频率
            if theoretical_freq < freq_range[0] or theoretical_freq > freq_range[1]:
                continue
                
            # 在检测到的峰值中寻找最接近理论频率的峰值
            for i, fc in enumerate(peak_freqs):
                if abs(fc - theoretical_freq) <= tolerance:
                    center_freqs.append({
                        'freq': fc,
                        'amplitude': peak_amplitudes[i],
                        'multiple': multiple
                    })
                    break
        
        # 收集所有边频
        sidebands = []
        for cf_info in center_freqs:
            cf = cf_info['freq']
            
            # 检查左右边频是否存在
            left_sb_freq = cf - fb
            right_sb_freq = cf + fb
            
            # 跳过超出频率范围的边频
            if left_sb_freq < freq_range[0] or right_sb_freq > freq_range[1]:
                continue
                
            # 查找左边频
            left_sb_found = False
            for i, p_freq in enumerate(peak_freqs):
                if abs(p_freq - left_sb_freq) <= bandwidth/2:
                    left_sb_found = True
                    left_sb_amp = peak_amplitudes[i]
                    left_sb_freq = p_freq  # 使用实际峰值频率
                    break
            
            # 查找右边频
            right_sb_found = False
            for i, p_freq in enumerate(peak_freqs):
                if abs(p_freq - right_sb_freq) <= bandwidth/2:
                    right_sb_found = True
                    right_sb_amp = peak_amplitudes[i]
                    right_sb_freq = p_freq  # 使用实际峰值频率
                    break
            
            # 只有当左右边频都存在时才添加到结果
            if left_sb_found and right_sb_found:
                sidebands.append({
                    'center_freq': cf,
                    'left_freq': left_sb_freq,
                    'right_freq': right_sb_freq,
                    'left_amp': left_sb_amp,
                    'right_amp': right_sb_amp
                })
        
        # 如果没有发现边频对，返回0
        if len(sidebands) == 0:
            return 0.0
        
        # 计算边频能量
        sideband_energy = 0
        for sb in sidebands:
            # 查找左边频能量
            left_idx = np.abs(freq_limited - sb['left_freq']).argmin()
            left_start = max(0, left_idx - int(bandwidth/2 * N/fs))
            left_end = min(len(freq_limited), left_idx + int(bandwidth/2 * N/fs))
            sideband_energy += np.sum(magnitude_limited[left_start:left_end])
            
            # 查找右边频能量
            right_idx = np.abs(freq_limited - sb['right_freq']).argmin()
            right_start = max(0, right_idx - int(bandwidth/2 * N/fs))
            right_end = min(len(freq_limited), right_idx + int(bandwidth/2 * N/fs))
            sideband_energy += np.sum(magnitude_limited[right_start:right_end])
        
        # 计算总能量（不包括基频及其谐波）
        total_energy = np.sum(magnitude_limited)
        
        # 计算基频及其谐波的能量
        base_freq_energy = 0
        for cf_info in center_freqs:
            cf = cf_info['freq']
            cf_idx = np.abs(freq_limited - cf).argmin()
            cf_start = max(0, cf_idx - int(bandwidth/2 * N/fs))
            cf_end = min(len(freq_limited), cf_idx + int(bandwidth/2 * N/fs))
            base_freq_energy += np.sum(magnitude_limited[cf_start:cf_end])
        
        # 调整总能量（去除基频能量）
        adjusted_total_energy = total_energy - base_freq_energy
        
        # 计算边频能量比
        sideband_energy_ratio = 0.0
        if adjusted_total_energy > 0:
            sideband_energy_ratio = 100.0 * sideband_energy / adjusted_total_energy
        
        return sideband_energy_ratio
        
    except Exception as e:
        logger.error(f"计算边频能量比时出错: {str(e)}")
        return 0.0

def save_sideband_energy_plot(timestamps, energy_ratios, table_name):
    """
    创建边频能量比随时间变化的图表，并标记超过阈值的点
    """
    # 设置警告和告警阈值
    warning_threshold = 1.0  # 警告阈值 (%)
    alert_threshold = 2.5    # 告警阈值 (%)
    
    plt.figure(figsize=(15, 6))
    
    # 计算超过阈值的点
    warning_points = [i for i, ratio in enumerate(energy_ratios) if ratio >= warning_threshold and ratio < alert_threshold]
    alert_points = [i for i, ratio in enumerate(energy_ratios) if ratio >= alert_threshold]
    
    # 绘制能量比曲线
    plt.plot(timestamps, energy_ratios, 'k-', linewidth=1.5)
    
    # 添加阈值线
    plt.axhline(y=warning_threshold, color='r', linestyle='--', label=f'警告阈值 ({warning_threshold}%)')
    plt.axhline(y=alert_threshold, color='b', linestyle='--', label=f'告警阈值 ({alert_threshold}%)')
    
    # 在图右侧显示阈值标签
    plt.text(len(timestamps)-1, warning_threshold, f"{warning_threshold}%", 
             color='red', ha='right', va='bottom')
    plt.text(len(timestamps)-1, alert_threshold, f"{alert_threshold}%", 
             color='blue', ha='right', va='bottom')
    
    # 标记超过阈值的点
    if warning_points:
        plt.plot([timestamps[i] for i in warning_points], 
                [energy_ratios[i] for i in warning_points], 
                'ro', markersize=6, label=f'警告点 ({len(warning_points)}个)')
    
    if alert_points:
        plt.plot([timestamps[i] for i in alert_points], 
                [energy_ratios[i] for i in alert_points], 
                'bo', markersize=6, label=f'告警点 ({len(alert_points)}个)')
    
    # 设置图表属性
    plt.title(f"边频能量比随时间变化 - {table_name}", fontsize=14)
    plt.xlabel("时间", fontsize=12)
    plt.ylabel("边频能量比 (%)", fontsize=12)
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.xticks(rotation=45, ha='right')
    
    # 设置y轴范围
    max_ratio = max(max(energy_ratios), alert_threshold) * 1.1
    plt.ylim(0, max_ratio)
    
    # 添加图例
    plt.legend(loc='upper right')
    
    plt.tight_layout()
    
    # 生成唯一文件名并保存
    file_name = f"sideband_energy_{table_name}_{uuid.uuid4().hex[:8]}.png"
    file_path = os.path.join(IMAGES_DIR, file_name)
    plt.savefig(file_path, format='png', dpi=150)
    plt.close()
    
    # 返回文件URL和统计信息
    return {
        'plot_url': get_image_url(file_name),
        'warning_count': len(warning_points),
        'alert_count': len(alert_points),
        'warning_threshold': warning_threshold,
        'alert_threshold': alert_threshold
    }

def analyze_sideband_energy_ratio(table_name):
    """
    分析表中所有时间段的边频能量比
    
    参数:
    table_name: 表名
    
    返回:
    包含图表URL和边频能量比数据的字典
    """
    try:
        logger.info(f"开始分析表 {table_name} 的边频能量比")
        
        # 获取表中所有时间段的数据
        time_segments = get_all_timeseries_data(table_name)
        
        if not time_segments:
            logger.warning(f"表 {table_name} 中没有找到有效的时间段数据")
            return {
                "error": f"表 {table_name} 中没有找到有效的时间段数据"
            }
        
        # 计算每个时间段的边频能量比
        timestamps = []
        energy_ratios = []
        detailed_data = []
        
        for segment in time_segments:
            # 计算边频能量比
            ser = calculate_sideband_energy_ratio(
                segment['values'], 
                fs=8192, 
                base_freq=50, 
                fb=7.5,
                freq_range=(0, 1000),
                bandwidth=2
            )
            
            # 记录结果
            timestamps.append(segment['timestamp'])
            energy_ratios.append(ser)
            
            detailed_data.append({
                'timestamp': segment['timestamp'],
                'sideband_energy_ratio': ser,
                'data_points': len(segment['values'])
            })
            
            logger.info(f"时间段 {segment['timestamp']} 的边频能量比: {ser:.4f}%")
        
        # 如果没有有效数据，返回错误
        if not timestamps:
            logger.warning(f"表 {table_name} 中没有生成有效的边频能量比数据")
            return {
                "error": f"表 {table_name} 中没有生成有效的边频能量比数据"
            }
        
        # 保存图表
        plot_info = save_sideband_energy_plot(timestamps, energy_ratios, table_name)
        
        # 准备返回结果
        result = {
            "table_name": table_name,
            "sideband_energy_plot_url": plot_info['plot_url'],
            "sideband_energy_data": detailed_data,
            "warning_threshold": plot_info['warning_threshold'],
            "alert_threshold": plot_info['alert_threshold'],
            "warning_count": plot_info['warning_count'],
            "alert_count": plot_info['alert_count'],
            "data_segments_count": len(time_segments)
        }
        
        logger.info(f"完成分析表 {table_name} 的边频能量比，共 {len(time_segments)} 个时间段")
        return result
    
    except Exception as e:
        logger.error(f"分析表 {table_name} 的边频能量比时出错: {str(e)}")
        return {"error": str(e)}

@app.post("/analyze/sideband-energy", response_model=SidebandEnergyResponse)
def analyze_sideband_energy_api(request: SidebandRequest):
    """
    分析指定表中所有时间段的边频能量比，生成随时间变化的图表
    
    参数:
    - table_name: 表名
    
    返回:
    - sideband_energy_plot_url: 边频能量比图表URL
    - sideband_energy_data: 详细的边频能量比数据
    - warning_threshold: 警告阈值
    - alert_threshold: 告警阈值
    - warning_count: 警告点数量
    - alert_count: 告警点数量
    """
    try:
        logger.info(f"收到分析边频能量比请求，表名: {request.table_name}")
        result = analyze_sideband_energy_ratio(request.table_name)
        
        if "error" in result:
            raise HTTPException(status_code=404, detail=result["error"])
        
        return result
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"分析边频能量比时出错: {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/mutationAnalysis", summary="突变分析", description="基于溶解气体分析突变分析", response_description="返回分析结果")
async def mutation_analysis_api(mutationAnalysis: MutationAnalysis):
    """气体浓度突变分析
    
    参数:
        mutationAnalysis: 包含两次取样数据的对象
            - first_sample_concentration: 第一次取样浓度
            - first_sample_time: 第一次取样时间
            - second_sample_concentration: 第二次取样浓度  
            - second_sample_time: 第二次取样时间
    
    返回:
        分析结果
    """
    try:
        logger.info(f"收到突变分析请求: {mutationAnalysis}")
        
        # 计算时间间隔(天数)
        time_diff = (mutationAnalysis.second_sample_time - mutationAnalysis.first_sample_time).total_seconds() / 86400
        
        # 计算浓度差值
        change_rate = mutationAnalysis.second_sample_concentration - mutationAnalysis.first_sample_concentration

        # 计算突变
        result = change_rate / time_diff
        
        logger.info(f"突变分析结果: {result}")
        return success(data={"result": result})
        
    except Exception as e:
        error_detail = traceback.format_exc()
        logger.error(f"突变分析失败: {error_detail}")
        return fail(msg=f"突变分析失败: {str(e)}")

# 添加API响应帮助函数
def success(data=None, msg="操作成功"):
    """返回成功响应"""
    return {
        "code": 200,
        "msg": msg,
        "data": data
    }

def fail(code=400, msg="操作失败", data=None):
    """返回失败响应"""
    return {
        "code": code,
        "msg": msg,
        "data": data
    }

def main():
    import uvicorn
    uvicorn.run(
        "td_analyzer:app", 
        host=API_CONFIG['host'], 
        port=API_CONFIG['port'], 
        reload=True
    )

if __name__ == "__main__":
    main() 