"""
音频分块服务 - 用于大文件处理与OpenAI Whisper API集成

该模块提供将大音频文件分割成符合OpenAI 25MB文件大小限制的小块的功能，
单独处理每个块，并在保持准确性和说话人连续性的同时重新组装转录结果。 
"""

import os
import json
import subprocess
import tempfile
import logging
import math
import re
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
import mimetypes

# 配置日志
logger = logging.getLogger(__name__)

class AudioChunkingService:
    """用于对大型音频文件进行分块并使用 OpenAI Whisper API 处理的服务。"""
    
    def __init__(self, max_chunk_size_mb: int = 20, overlap_seconds: int = 3, max_chunk_duration_seconds: int = None):
        """
        初始化分块服务。

        Args:
            max_chunk_size_mb: 每个分块的最大大小，单位为 MB（默认 20MB，留有余量）
            overlap_seconds: 分块之间的重叠时长，单位为秒，用于保持上下文连续性
            max_chunk_duration_seconds: 每个分块的最大时长，单位为秒（可选）
        """
        self.max_chunk_size_mb = max_chunk_size_mb
        self.overlap_seconds = overlap_seconds
        self.max_chunk_size_bytes = max_chunk_size_mb * 1024 * 1024
        self.max_chunk_duration_seconds = max_chunk_duration_seconds
        self.chunk_stats = []  # 跟踪处理统计信息
        
    def needs_chunking(self, file_path: str, use_asr_endpoint: bool = False) -> bool:
        """
        根据文件大小和使用的端点检查文件是否需要分块。

        注意：对于基于时长的限制，此方法可能返回 True，即使实际上不需要分块，
        因为我们需要先转换文件才能检查时长。实际的分块决策是在转换后通过
        calculate_optimal_chunking() 方法做出的。

        Args:
            file_path: 音频文件的路径
            use_asr_endpoint: 是否使用 ASR 端点（不需要分块）

        Returns:
            如果文件可能需要分块则返回 True，否则返回 False
        """
        if use_asr_endpoint:
            return False
            
        try:
            file_size = os.path.getsize(file_path)
            mode, limit_value = self.parse_chunk_limit()
            
            if mode == 'size':
                # 对于基于大小的限制，可以立即判断
                chunk_size_bytes = limit_value * 1024 * 1024
                needs_it = file_size > chunk_size_bytes
                logger.info(f"文件大小检查：{file_size/1024/1024:.1f}MB vs 限制 {limit_value}MB - 需要分块：{needs_it}")
                return needs_it
            else:
                # 对于基于时长的限制，需要检查实际时长
                # 先尝试不转换直接获取时长（快速检查）
                duration = self.get_audio_duration(file_path)
                if duration:
                    needs_it = duration > limit_value
                    logger.info(f"文件时长检查：{duration:.1f}s vs 限制 {limit_value}s - 需要分块：{needs_it}")
                    return needs_it
                else:
                    # 无法在不转换的情况下确定时长，假设可能需要分块
                    logger.info(f"已设置基于时长的限制（{limit_value}s），但无法直接获取时长 - 将在转换后检查")
                    return True  # 继续转换并检查
        except OSError:
            logger.error(f"无法获取文件大小：{file_path}")
            return False
    
    def get_audio_duration(self, file_path: str) -> Optional[float]:
        """
        使用 ffprobe 获取音频文件的时长（秒）。

        Args:
            file_path: 音频文件的路径

        Returns:
            时长（秒），如果无法确定则返回 None
        """
        try:
            result = subprocess.run([
                'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
                '-of', 'default=noprint_wrappers=1:nokey=1', file_path
            ], capture_output=True, text=True, check=True)
            
            duration = float(result.stdout.strip())
            return duration
        except (subprocess.CalledProcessError, ValueError, FileNotFoundError) as e:
            logger.error(f"获取音频时长失败：{file_path}，错误：{e}")
            return None
    
    def convert_to_mp3_and_get_info(self, file_path: str, temp_dir: str) -> Tuple[str, float, float]:
        """
        将输入文件转换为 MP3 格式以保持一致性，并获取其大小和时长信息。

        Args:
            file_path: 源音频文件的路径
            temp_dir: 存储临时 MP3 文件的目录

        Returns:
            元组 (mp3_file_path, duration_seconds, size_bytes)
        """
        try:
            # 生成 MP3 文件名
            base_name = os.path.splitext(os.path.basename(file_path))[0]
            mp3_filename = f"{base_name}_converted.mp3"
            mp3_path = os.path.join(temp_dir, mp3_filename)
            
            # 转换为高质量 MP3 以提高转录准确性
            cmd = [
                'ffmpeg', '-i', file_path,
                '-codec:a', 'libmp3lame',  # 明确使用 LAME MP3 编码器
                '-b:a', '128k',  # 128kbps 比特率以保证高质量
                '-ar', '44100',  # 44.1kHz 采样率以获得更好质量
                '-ac', '1',  # 单声道（对语音足够）
                '-compression_level', '2',  # 更好的压缩
                '-y',  # 覆盖输出文件
                mp3_path
            ]
            
            logger.info(f"正在将 {file_path} 转换为 128kbps MP3 格式以便准确分块...")
            result = subprocess.run(cmd, capture_output=True, text=True)
            if result.returncode != 0:
                raise ValueError(f"ffmpeg conversion failed: {result.stderr}")
            
            if not os.path.exists(mp3_path):
                raise ValueError("MP3 file was not created")
            
            # 获取转换后 MP3 文件的大小和时长
            mp3_size = os.path.getsize(mp3_path)
            mp3_duration = self.get_audio_duration(mp3_path)
            
            if not mp3_duration:
                raise ValueError("Could not determine MP3 file duration")
            
            logger.info(f"MP3 转换完成：{mp3_size/1024/1024:.1f}MB，{mp3_duration:.1f}秒")
            
            # 可选：保留转换后的文件用于调试（在环境变量中设置 PRESERVE_CHUNK_DEBUG=true）
            if os.getenv('PRESERVE_CHUNK_DEBUG', 'false').lower() == 'true':
                import shutil
                # 将调试文件保存到 /data/uploads/debug/ 目录
                debug_dir = '/data/uploads/debug'
                os.makedirs(debug_dir, exist_ok=True)
                debug_filename = os.path.basename(mp3_path).replace('_converted', '_converted_debug')
                debug_path = os.path.join(debug_dir, debug_filename)
                shutil.copy2(mp3_path, debug_path)
                logger.info(f"调试：已保留转换后的文件：{debug_path}")
            
            return mp3_path, mp3_duration, mp3_size
            
        except Exception as e:
            logger.error(f"音频转 MP3 失败：{e}")
            raise

    def parse_chunk_limit(self) -> Tuple[str, float]:
        """
        解析 CHUNK_LIMIT 环境变量以确定分块模式和值。

        支持的格式：
        - 基于大小："20MB"、"10MB"
        - 基于时长："1200s"、"20m"
        - 旧版：CHUNK_SIZE_MB 环境变量（向后兼容）

        Returns:
            元组 (mode, value)，其中 mode 为 'size' 或 'duration'
        """
        chunk_limit = os.environ.get('CHUNK_LIMIT', '').strip().upper()
        
        # 检查新的 CHUNK_LIMIT 格式
        if chunk_limit:
            # 基于大小：以 MB 结尾
            if chunk_limit.endswith('MB'):
                try:
                    size_mb = float(re.sub(r'[^0-9.]', '', chunk_limit))
                    return 'size', size_mb
                except ValueError:
                    logger.warning(f"CHUNK_LIMIT 格式无效：{chunk_limit}")
            
            # 基于时长：以 s 或 m 结尾
            elif chunk_limit.endswith('S'):
                try:
                    seconds = float(re.sub(r'[^0-9.]', '', chunk_limit))
                    return 'duration', seconds
                except ValueError:
                    logger.warning(f"CHUNK_LIMIT 格式无效：{chunk_limit}")
            
            elif chunk_limit.endswith('M'):
                try:
                    minutes = float(re.sub(r'[^0-9.]', '', chunk_limit))
                    return 'duration', minutes * 60
                except ValueError:
                    logger.warning(f"CHUNK_LIMIT 格式无效：{chunk_limit}")
        
        # 回退到旧版 CHUNK_SIZE_MB 以保持向后兼容
        legacy_size = os.environ.get('CHUNK_SIZE_MB', '20')
        try:
            size_mb = float(legacy_size)
            logger.info(f"使用旧版 CHUNK_SIZE_MB 配置：{size_mb}MB")
            return 'size', size_mb
        except ValueError:
            logger.warning(f"CHUNK_SIZE_MB 格式无效：{legacy_size}")
            return 'size', 20.0  # 最终回退值
    
    def calculate_optimal_chunking(self, converted_size: float, total_duration: float) -> Tuple[int, float]:
        """
        根据配置的限制计算最优的分块数量和分块时长。

        Args:
            converted_size: 转换后音频文件的大小（字节）
            total_duration: 音频文件的总时长（秒）

        Returns:
            元组 (num_chunks, chunk_duration_seconds)
        """
        try:
            mode, limit_value = self.parse_chunk_limit()
            
            if mode == 'size':
                # 基于大小的分块
                max_size_bytes = limit_value * 1024 * 1024 * 0.95  # 95% 安全系数
                num_chunks = max(1, math.ceil(converted_size / max_size_bytes))
                
                logger.info(f"基于大小分块：限制 {limit_value}MB")
                logger.info(f"文件大小 {converted_size/1024/1024:.1f}MB 需要分成 {num_chunks} 块")
                
            else:  # duration-based
                # 基于时长的分块，带有 API 安全限制
                effective_limit = min(limit_value, 1400)  # 限制在 OpenAI 安全范围内
                num_chunks = max(1, math.ceil(total_duration / effective_limit))
                
                logger.info(f"基于时长分块：限制 {limit_value}秒（有效限制：{effective_limit}秒）")
                logger.info(f"文件时长 {total_duration:.1f}秒 需要分成 {num_chunks} 块")
            
            # 计算分块时长
            chunk_duration = total_duration / num_chunks
            
            # 应用最小时长（5 分钟），但不超过文件总时长
            chunk_duration = min(max(300, chunk_duration), total_duration)
            
            # 记录最终分块计划
            expected_chunk_size_mb = (converted_size / num_chunks) / (1024 * 1024)
            logger.info(f"分块计划：{num_chunks} 块，每块约 {chunk_duration:.1f}秒（约 {expected_chunk_size_mb:.1f}MB）")
            
            return num_chunks, chunk_duration
            
        except Exception as e:
            logger.error(f"计算最优分块策略失败：{e}")
            # 保守回退方案
            fallback_chunks = max(2, math.ceil(total_duration / 600))  # 10 分钟一块
            fallback_duration = total_duration / fallback_chunks
            return fallback_chunks, fallback_duration
    
    def create_chunks(self, file_path: str, temp_dir: str) -> List[Dict[str, Any]]:
        """
        将音频文件分割成重叠的分块。

        首先将文件转换为 MP3 格式以获取准确的大小信息，
        然后根据实际 MP3 文件大小计算最优分块时长。

        Args:
            file_path: 源音频文件的路径
            temp_dir: 存储临时分块文件的目录

        Returns:
            分块信息字典列表
        """
        chunks = []
        wav_path = None
        
        try:
            # 步骤 1：转换为 MP3 并获取准确的大小/时长信息
            mp3_path, mp3_duration, mp3_size = self.convert_to_mp3_and_get_info(file_path, temp_dir)
            
            # 步骤 2：计算最优分块策略
            num_chunks, chunk_duration = self.calculate_optimal_chunking(mp3_size, mp3_duration)
            
            # 如果只需要 1 个分块，则无需实际分块
            if num_chunks == 1:
                logger.info(f"文件时长 {mp3_duration:.1f}秒在限制范围内 - 无需分块")
                # 将整个文件作为单个"分块"返回
                base_name = os.path.splitext(os.path.basename(file_path))[0]
                chunk_filename = f"{base_name}_chunk_000.mp3"
                chunk_path = os.path.join(temp_dir, chunk_filename)
                
                # 将转换后的文件复制为单个分块
                import shutil
                shutil.copy2(mp3_path, chunk_path)
                
                chunk_info = {
                    'index': 0,
                    'path': chunk_path,
                    'filename': chunk_filename,
                    'start_time': 0,
                    'end_time': mp3_duration,
                    'duration': mp3_duration,
                    'size_bytes': mp3_size,
                    'size_mb': mp3_size / (1024 * 1024)
                }
                chunks.append(chunk_info)
                logger.info(f"已创建单个分块，时长：{mp3_duration:.1f}秒")
                return chunks
            
            # 计算步长以创建恰好 num_chunks 个分块并带有重叠
            # 需要的总覆盖范围：mp3_duration + (overlap * (num_chunks - 1))
            # 每个分块覆盖：chunk_duration
            # 分块之间的步长以恰好得到 num_chunks 个分块
            if num_chunks > 1:
                step_duration = (mp3_duration - chunk_duration) / (num_chunks - 1)
            else:
                step_duration = mp3_duration
            
            current_start = 0
            chunk_index = 0
            
            logger.info(f"正在将 {file_path} 分割为 {num_chunks} 块，每块约 {chunk_duration:.1f}秒，重叠 {self.overlap_seconds}秒")
            
            for chunk_index in range(num_chunks):
                # 计算此分块的起始位置
                if chunk_index > 0:
                    current_start = chunk_index * step_duration
                
                # 计算此分块的结束时间
                chunk_end = min(current_start + chunk_duration, mp3_duration)
                actual_duration = chunk_end - current_start
                
                # 跳过末尾过短的分块（正确计算下不应发生）
                if actual_duration < 10:  # 小于 10 秒
                    logger.warning(f"跳过过短的分块 {chunk_index}：{actual_duration:.1f}秒")
                    break
                
                # 生成分块文件名
                base_name = os.path.splitext(os.path.basename(file_path))[0]
                chunk_filename = f"{base_name}_chunk_{chunk_index:03d}.mp3"
                chunk_path = os.path.join(temp_dir, chunk_filename)
                
                # 从转换后的 MP3 文件中提取分块（比重新转换更高效）
                cmd = [
                    'ffmpeg', '-i', mp3_path,
                    '-ss', str(current_start),
                    '-t', str(actual_duration),
                    '-acodec', 'copy',  # 复制编解码器，因为已经是正确的格式
                    '-y',  # 覆盖输出文件
                    chunk_path
                ]
                
                result = subprocess.run(cmd, capture_output=True, text=True)
                if result.returncode != 0:
                    logger.error(f"FFmpeg 分块失败（分块 {chunk_index}）：{result.stderr}")
                    continue
                
                # 验证分块已创建并获取其大小
                if os.path.exists(chunk_path):
                    chunk_size = os.path.getsize(chunk_path)
                    
                    # 验证分块大小是否在限制范围内
                    if chunk_size > self.max_chunk_size_bytes:
                        logger.warning(f"分块 {chunk_index} 大小为 {chunk_size/1024/1024:.1f}MB，超过限制 {self.max_chunk_size_mb}MB")
                    
                    chunk_info = {
                        'index': chunk_index,
                        'path': chunk_path,
                        'filename': chunk_filename,
                        'start_time': current_start,
                        'end_time': chunk_end,
                        'duration': actual_duration,
                        'size_bytes': chunk_size,
                        'size_mb': chunk_size / (1024 * 1024)
                    }
                    
                    chunks.append(chunk_info)
                    logger.info(f"已创建分块 {chunk_index}：{current_start:.1f}秒-{chunk_end:.1f}秒（{chunk_size/1024/1024:.1f}MB）")
                    
                    # 可选：保留分块用于调试（在环境变量中设置 PRESERVE_CHUNK_DEBUG=true）
                    if os.getenv('PRESERVE_CHUNK_DEBUG', 'false').lower() == 'true':
                        import shutil
                        # 将调试分块保存到 /data/uploads/debug/ 目录
                        debug_dir = '/data/uploads/debug'
                        os.makedirs(debug_dir, exist_ok=True)
                        debug_filename = os.path.basename(chunk_path).replace('.mp3', '_debug.mp3')
                        debug_path = os.path.join(debug_dir, debug_filename)
                        shutil.copy2(chunk_path, debug_path)
                        logger.info(f"调试：已保留分块文件：{debug_path}")
                else:
                    logger.error(f"分块文件未创建：{chunk_path}")
            
            logger.info(f"已为 {file_path} 创建 {len(chunks)} 个分块")
            return chunks
            
        except Exception as e:
            logger.error(f"创建分块失败（{file_path}）：{e}")
            # 清理任何部分创建的分块
            for chunk in chunks:
                try:
                    if os.path.exists(chunk['path']):
                        os.remove(chunk['path'])
                except Exception:
                    pass
            raise
        finally:
            # 清理临时 WAV 文件
            if wav_path and os.path.exists(wav_path):
                try:
                    os.remove(wav_path)
                    logger.debug(f"已清理临时 WAV 文件：{wav_path}")
                except Exception as e:
                    logger.warning(f"清理临时 WAV 文件失败：{e}")
    
    def merge_transcriptions(self, chunk_results: List[Dict[str, Any]]) -> str:
        """
        合并多个分块的转录结果，处理重叠部分。

        Args:
            chunk_results: 分块转录结果列表

        Returns:
            合并后的转录文本
        """
        if not chunk_results:
            return ""
        
        if len(chunk_results) == 1:
            return chunk_results[0].get('transcription', '')
        
        # 按开始时间排序分块以确保正确顺序
        sorted_chunks = sorted(chunk_results, key=lambda x: x.get('start_time', 0))
        
        merged_text = ""
        
        for i, chunk in enumerate(sorted_chunks):
            chunk_text = chunk.get('transcription', '').strip()
            
            if not chunk_text:
                continue
            
            if i == 0:
                # 第一个分块：使用完整转录
                merged_text = chunk_text
            else:
                # 后续分块：尝试处理重叠
                merged_text = self._merge_overlapping_text(
                    merged_text, 
                    chunk_text, 
                    chunk.get('start_time', 0),
                    sorted_chunks[i-1].get('end_time', 0)
                )
        
        return merged_text
    
    def _merge_overlapping_text(self, existing_text: str, new_text: str, 
                               new_start_time: float, prev_end_time: float) -> str:
        """
        合并重叠的转录文本，尝试去除重复内容。

        Args:
            existing_text: 之前合并的文本
            new_text: 要合并的新分块文本
            new_start_time: 新分块的开始时间
            prev_end_time: 上一个分块的结束时间

        Returns:
            处理重叠后的合并文本
        """
        # 如果没有重叠，直接连接
        overlap_duration = prev_end_time - new_start_time
        if overlap_duration <= 0:
            return f"{existing_text}\n{new_text}"
        
        # 对于重叠的分块，尝试找到共同文本并智能合并
        # 这是一种简化的方法 - 实际上可能需要更复杂的文本相似度匹配
        
        # 将文本分割成句子/短语
        existing_sentences = self._split_into_sentences(existing_text)
        new_sentences = self._split_into_sentences(new_text)
        
        if not existing_sentences or not new_sentences:
            return f"{existing_text}\n{new_text}"
        
        # 通过比较现有文本的最后几个句子和新文本的前几个句子来尝试找到重叠
        overlap_found = False
        merge_point = len(existing_sentences)
        
        # 查找共同句子（简单方法）
        for i in range(min(3, len(existing_sentences))):  # 检查最后 3 个句子
            last_sentence = existing_sentences[-(i+1)].strip().lower()
            
            for j in range(min(3, len(new_sentences))):  # 检查前 3 个句子
                first_sentence = new_sentences[j].strip().lower()
                
                # 如果句子足够相似，则认为是重叠
                if last_sentence and first_sentence and self._sentences_similar(last_sentence, first_sentence):
                    merge_point = len(existing_sentences) - i
                    new_start_index = j + 1
                    overlap_found = True
                    break
            
            if overlap_found:
                break
        
        if overlap_found:
            # 在找到的重叠点合并
            merged_sentences = existing_sentences[:merge_point] + new_sentences[new_start_index:]
            return ' '.join(merged_sentences)
        else:
            # 没有找到明确的重叠，用分隔符连接
            return f"{existing_text}\n{new_text}"
    
    def _split_into_sentences(self, text: str) -> List[str]:
        """将文本分割成句子用于重叠检测。"""
        import re
        # 简单的句子分割 - 可以使用更复杂的 NLP 方法改进
        sentences = re.split(r'[.!?]+', text)
        return [s.strip() for s in sentences if s.strip()]
    
    def _sentences_similar(self, sent1: str, sent2: str, threshold: float = 0.8) -> bool:
        """检查两个句子是否足够相似以被视为相同。"""
        # 基于共同词的简单相似度检查
        words1 = set(sent1.split())
        words2 = set(sent2.split())
        
        if not words1 or not words2:
            return False
        
        intersection = len(words1.intersection(words2))
        union = len(words1.union(words2))
        
        similarity = intersection / union if union > 0 else 0
        return similarity >= threshold
    
    def analyze_chunk_audio_properties(self, chunk_path: str) -> Dict[str, Any]:
        """
        当前未被使用，保留接口。

        分析分块的音频属性，比如时长、大小、码率、采样率、声道数、编码器、采样位数等，这些属性可能影响处理时间。

        Args:
            chunk_path: 分块文件的路径

        Returns:
            包含音频分析结果的字典
        """
        try:
            # 使用 ffprobe 获取详细的音频信息
            cmd = [
                'ffprobe', '-v', 'quiet', '-print_format', 'json',
                '-show_format', '-show_streams', chunk_path
            ]
            
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            probe_data = json.loads(result.stdout)
            
            audio_stream = None
            for stream in probe_data.get('streams', []):
                if stream.get('codec_type') == 'audio':
                    audio_stream = stream
                    break
            
            if not audio_stream:
                return {'error': 'No audio stream found'}
            
            format_info = probe_data.get('format', {})
            
            analysis = {
                'duration': float(format_info.get('duration', 0)),
                'size_bytes': int(format_info.get('size', 0)),
                'bitrate': int(format_info.get('bit_rate', 0)),
                'sample_rate': int(audio_stream.get('sample_rate', 0)),
                'channels': int(audio_stream.get('channels', 0)),
                'codec': audio_stream.get('codec_name', 'unknown'),
                'bits_per_sample': int(audio_stream.get('bits_per_raw_sample', 0)),
            }
            
            # 计算一些派生指标
            if analysis['duration'] > 0:
                analysis['effective_bitrate'] = (analysis['size_bytes'] * 8) / analysis['duration']
                analysis['compression_ratio'] = analysis['bitrate'] / analysis['effective_bitrate'] if analysis['effective_bitrate'] > 0 else 0
            
            return analysis
            
        except Exception as e:
            logger.warning(f"分析分块音频属性失败：{e}")
            return {'error': str(e)}
    
    def log_processing_statistics(self, chunk_results: List[Dict[str, Any]]) -> None:
        """
        记录关于分块处理性能的详细统计信息。

        Args:
            chunk_results: 包含时间信息的分块处理结果列表
        """
        if not chunk_results:
            return
        
        logger.info("=== 分块处理统计 ===")
        
        total_chunks = len(chunk_results)
        processing_times = []
        sizes = []
        durations = []
        
        for i, result in enumerate(chunk_results):
            processing_time = result.get('processing_time', 0)
            chunk_size = result.get('size_mb', 0)
            chunk_duration = result.get('duration', 0)
            
            processing_times.append(processing_time)
            sizes.append(chunk_size)
            durations.append(chunk_duration)
            
            # 记录单个分块统计信息
            rate = chunk_duration / processing_time if processing_time > 0 else 0
            logger.info(f"分块 {i+1}：处理耗时 {processing_time:.1f}秒，大小 {chunk_size:.1f}MB，音频时长 {chunk_duration:.1f}秒（处理速率：{rate:.2f}倍）")
        
        # 计算汇总统计信息
        if processing_times:
            avg_time = sum(processing_times) / len(processing_times)
            min_time = min(processing_times)
            max_time = max(processing_times)
            
            avg_size = sum(sizes) / len(sizes)
            avg_duration = sum(durations) / len(durations)
            
            total_audio_time = sum(durations)
            total_processing_time = sum(processing_times)
            overall_rate = total_audio_time / total_processing_time if total_processing_time > 0 else 0
            
            logger.info(f"汇总：{total_chunks} 个分块，{total_audio_time:.1f}秒音频，处理耗时 {total_processing_time:.1f}秒")
            logger.info(f"平均：处理耗时 {avg_time:.1f}秒，大小 {avg_size:.1f}MB，音频时长 {avg_duration:.1f}秒")
            logger.info(f"范围：处理耗时 {min_time:.1f}秒 - {max_time:.1f}秒")
            logger.info(f"总体处理速率：{overall_rate:.2f}倍实时")
            
            # 识别性能异常值
            if max_time > avg_time * 2:
                slow_chunks = [i for i, t in enumerate(processing_times) if t > avg_time * 1.5]
                logger.warning(f"检测到性能异常分块：{[i+1 for i in slow_chunks]} 号分块处理耗时显著偏高")
                
                # 建议可能的原因
                logger.info("处理缓慢的可能原因：")
                logger.info("- OpenAI API 服务器负载/性能波动")
                logger.info("- 网络延迟或连接问题")
                logger.info("- 音频内容复杂度（静音、噪音、多说话人）")
                logger.info("- 临时 API 限流或限速")
        
        logger.info("=== 统计结束 ===")
    
    def get_performance_recommendations(self, chunk_results: List[Dict[str, Any]]) -> List[str]:
        """
        根据处理结果生成性能建议。
        
        给出了建议，但调用者并未做后续调优逻辑，暂不明用途。

        Args:
            chunk_results: 分块处理结果列表

        Returns:
            建议字符串列表
        """
        recommendations = []
        
        if not chunk_results:
            return recommendations
        
        processing_times = [r.get('processing_time', 0) for r in chunk_results]
        
        if processing_times:
            avg_time = sum(processing_times) / len(processing_times)
            max_time = max(processing_times)
            
            # 检查处理时间的高方差
            if max_time > avg_time * 3:
                recommendations.append("检测到处理时间波动较大，建议实现指数退避重试逻辑。")
            
            # 检查整体处理速度过慢
            total_audio = sum(r.get('duration', 0) for r in chunk_results)
            total_processing = sum(processing_times)
            rate = total_audio / total_processing if total_processing > 0 else 0
            
            if rate < 0.5:  # 低于 0.5 倍实时速度
                recommendations.append("整体处理较慢，建议减小分块大小或考虑更换转录服务。")
            
            # 检查超时问题
            if any(t > 300 for t in processing_times):  # 超过 5 分钟
                recommendations.append("部分分块处理超过5分钟，建议实现超时处理和分块重试逻辑。")
            
            # 检查分块大小优化
            avg_size = sum(r.get('size_mb', 0) for r in chunk_results) / len(chunk_results)
            if avg_size < 10:
                recommendations.append("分块较小，建议增大大分块大小以提高处理效率。")
            elif avg_size > 22:
                recommendations.append("分块接近大小限制，建议减小分块大小以提高处理稳定性。")
        
        return recommendations
    
    def cleanup_chunks(self, chunks: List[Dict[str, Any]], temp_mp3_path: str = None) -> None:
        """
        清理临时分块文件和 MP3 文件。

        Args:
            chunks: 分块信息字典列表
            temp_mp3_path: 要清理的临时 MP3 文件路径（可选）
        """
        for chunk in chunks:
            try:
                chunk_path = chunk.get('path')
                if chunk_path and os.path.exists(chunk_path):
                    os.remove(chunk_path)
                    logger.debug(f"已清理分块文件：{chunk_path}")
            except Exception as e:
                logger.warning(f"清理分块失败（{chunk.get('filename', 'unknown')}）：{e}")
        
        # 清理临时 MP3 文件（如果提供）
        if temp_mp3_path and os.path.exists(temp_mp3_path):
            try:
                os.remove(temp_mp3_path)
                logger.debug(f"已清理临时 MP3 文件：{temp_mp3_path}")
            except Exception as e:
                logger.warning(f"清理临时 MP3 文件失败：{e}")

class ChunkProcessingError(Exception):
    """分块处理失败时引发的异常。"""
    pass

class ChunkingNotSupportedError(Exception):
    """当前配置不支持分块时引发的异常。"""
    pass
