"""
Transcription Service - 处理音频转录相关业务逻辑
"""
import os
import json
import time
import tempfile
import subprocess
import mimetypes
import logging
from pathlib import Path
from datetime import datetime

import httpx
from openai import OpenAI

from src.clients import db
from src.models import Recording, SystemSetting
from src.config import TRANSCRIPTION_API_KEY, TRANSCRIPTION_BASE_URL

logger = logging.getLogger(__name__)


def extract_audio_from_video(video_filepath, output_format='mp3', cleanup_original=True, app_logger=None):
    """使用 FFmpeg 从视频容器中提取音频
    
    使用 MP3 编解码器以获得最佳兼容性和可预测的文件大小。
    64kbps MP3 提供良好的语音质量，每分钟约 480KB。
    """
    _logger = app_logger or logger
    
    try:
        # 生成带有音频扩展名的输出文件名
        base_filepath, file_ext = os.path.splitext(video_filepath)
        temp_audio_filepath = f"{base_filepath}_audio_temp.{output_format}"
        final_audio_filepath = f"{base_filepath}_audio.{output_format}"
        
        _logger.info(f"从视频中提取音频：{video_filepath} -> {temp_audio_filepath}")
        
        # 使用 FFmpeg 提取音频 - 使用高质量 MP3 以获得更好的转录效果
        subprocess.run([
            'ffmpeg', '-i', video_filepath, '-y',
            '-vn',  # 不提取视频
            '-codec:a', 'libmp3lame',  # 明确使用 LAME MP3 编码器
            '-b:a', '128k',  # 128kbps 比特率，高质量
            '-ar', '44100',  # 44.1kHz 采样率，更好的质量
            '-ac', '1',  # 单声道（语音足够，减少文件大小）
            '-compression_level', '2',  # 更好的压缩
            temp_audio_filepath
        ], check=True, capture_output=True, text=True)
        
        _logger.info(f"音频提取成功：{temp_audio_filepath}")
        
        # 可选保留临时文件用于调试（在环境变量中设置 PRESERVE_TEMP_AUDIO=true）
        if os.getenv('PRESERVE_TEMP_AUDIO', 'false').lower() == 'true':
            import shutil
            shutil.copy2(temp_audio_filepath, temp_audio_filepath.replace('_temp', '_debug'))
            _logger.info(f"调试：已保留临时音频文件为 {temp_audio_filepath.replace('_temp', '_debug')}")
        
        # 将临时文件重命名为最终文件名
        os.rename(temp_audio_filepath, final_audio_filepath)
        
        # 如果要求，清理原始视频文件
        if cleanup_original:
            try:
                os.remove(video_filepath)
                _logger.info(f"已清理原始视频文件：{video_filepath}")
            except Exception as e:
                _logger.warning(f"清理原始视频文件 {video_filepath} 失败：{str(e)}")
        
        return final_audio_filepath, f'audio/{output_format}'
        
    except subprocess.CalledProcessError as e:
        _logger.error(f"FFmpeg 音频提取失败，文件 {video_filepath}：{e.stderr}")
        raise Exception(f"音频提取失败：{e.stderr}")
    except FileNotFoundError:
        _logger.error("未找到 FFmpeg 命令。请确保已安装 FFmpeg 并将其添加到系统 PATH 中。")
        raise Exception("服务器上未找到音频转换工具（FFmpeg）。")
    except Exception as e:
        _logger.error(f"从 {video_filepath} 提取音频时出错：{str(e)}")
        raise


def convert_to_wav(input_path, app_logger=None):
    """
    将音频文件转换为WAV格式
    Args:
        input_path: 输入音频文件路径
        app_logger: Flask app logger
    Returns:
        str: 转换后的WAV文件路径
    Raises:
        Exception: 转换失败时抛出异常
    """
    _logger = app_logger or logger
    
    # 检查文件是否存在
    if not os.path.exists(input_path):
        raise FileNotFoundError(f"音频文件不存在: {input_path}")
    
    # 获取文件MIME类型
    mime_type, _ = mimetypes.guess_type(input_path)
    _logger.info(f"文件 MIME 类型：{mime_type}，路径：{input_path}")
    # 如果是WAV格式，直接返回
    if mime_type == 'audio/wav' or input_path.lower().endswith('.wav'):
        return input_path
    
    # 创建临时文件用于存储转换后的WAV
    temp_dir = tempfile.gettempdir()
    output_filename = Path(input_path).stem + '_converted.wav'
    output_path = os.path.join(temp_dir, output_filename)
    
    try:
        # 使用ffmpeg进行格式转换
        cmd = [
            'ffmpeg', '-y', '-i', input_path,
            '-acodec', 'pcm_s16le',
            '-ar', '16000', 
            '-ac', '1',
            output_path
        ]
        
        # 执行转换命令
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        
        if result.returncode != 0:
            raise Exception(f"音频转换失败: {result.stderr}")
        
        # 检查输出文件是否生成成功
        if not os.path.exists(output_path):
            raise Exception("转换后的文件未生成")
            
        return output_path
        
    except subprocess.TimeoutExpired:
        raise Exception("音频转换超时")
    except Exception as e:
        # 清理可能生成的临时文件
        if os.path.exists(output_path):
            os.remove(output_path)
        raise Exception(f"音频转换错误: {str(e)}")


def transcribe_audio_asr(app_context, recording_id, filepath, original_filename, start_time,
                         mime_type=None, language=None, diarize=False, min_speakers=None, 
                         max_speakers=None, tag_id=None,
                         ASR_BASE_URL=None, USE_ASR_ENDPOINT=True, ASR_DIARIZE=False,
                         generate_title_task=None, generate_summary_only_task=None,
                         app_logger=None, UPLOAD_FOLDER=None):
    """使用 ASR 服务进行音频转录"""
    _logger = app_logger or logger
    
    with app_context:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            _logger.error(f"错误：未找到录音 {recording_id}，无法进行 ASR 转录。")
            return

        try:
            if not ASR_BASE_URL:
                raise ValueError("ASR_BASE_URL 未配置，无法调用外部 ASR 服务")

            _logger.info(f"开始对录音 {recording_id} 进行 ASR 转录...")
            recording.status = 'PROCESSING'
            db.session.commit()

            # 检查是否需要从视频容器中提取音频
            actual_filepath = filepath
            actual_content_type = mime_type or mimetypes.guess_type(original_filename)[0] or 'application/octet-stream'
            actual_filename = original_filename

            # 需要提取音频的视频 MIME 类型列表
            video_mime_types = [
                'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm',
                'video/avi', 'video/x-ms-wmv', 'video/3gpp'
            ]
            
            # 通过 MIME 类型或扩展名检查文件是否为视频容器
            is_video = (
                actual_content_type.startswith('video/') or 
                actual_content_type in video_mime_types or
                original_filename.lower().endswith(('.mp4', '.mov', '.avi', '.mkv', '.webm', '.wmv', '.3gp'))
            )
            
            if is_video:
                _logger.info(f"检测到视频容器（{actual_content_type}），正在提取音频...")
                try:
                    # 从视频中提取音频
                    audio_filepath, audio_mime_type = extract_audio_from_video(filepath, 'mp3', app_logger=_logger)
                    
                    # 更新路径和 MIME 类型用于 ASR 处理
                    actual_filepath = audio_filepath
                    actual_content_type = audio_mime_type
                    actual_filename = os.path.basename(audio_filepath)
                    
                    # 更新录音的音频路径和新的 MIME 类型
                    recording.audio_path = audio_filepath
                    recording.mime_type = audio_mime_type
                    db.session.commit()
                    
                    _logger.info(f"音频提取成功：{audio_filepath}")
                except Exception as e:
                    _logger.error(f"从视频中提取音频失败：{str(e)}")
                    recording.status = 'FAILED'
                    recording.error_message = f"音频提取失败：{str(e)}"
                    db.session.commit()
                    return

            # 跟踪是否已尝试过 WAV 转换
            wav_conversion_attempted = False
            wav_converted_filepath = None
            
            # 重试循环，用于处理 500 错误并进行 WAV 转换
            max_attempts = 2
            for attempt in range(max_attempts):
                try:
                    # 如果之前有转换过的 MP3 则使用
                    current_filepath = wav_converted_filepath if wav_converted_filepath else actual_filepath
                    current_content_type = 'audio/mpeg' if wav_converted_filepath else actual_content_type
                    current_filename = os.path.basename(current_filepath)
                    
                    with open(current_filepath, 'rb') as audio_file:
                        url = f"{ASR_BASE_URL}/asr"
                        params = {
                            'batch_size_s':300,
                            'spk_diarization':True,
                            'spk_num':0,
                            'spk_threshold':0.6,
                            'spk_cluster_method':'ahc',
                            'spk_smooth_window':3,
                            'spk_min_duration':1,
                            'spk_merge_duration':0.3
                        }

                        content_type = current_content_type
                        _logger.info(f"ASR 上传使用 MIME 类型：{content_type}")
                        files = {'audio_file': (current_filename, audio_file, content_type)}
                        
                        with httpx.Client() as client:
                            # 从数据库获取可配置的 ASR 超时时间（默认 30 分钟）
                            asr_timeout_seconds = SystemSetting.get_setting('asr_timeout_seconds', 18000)
                            timeout = httpx.Timeout(None, connect=30.0, read=float(asr_timeout_seconds), write=30.0, pool=30.0)
                            _logger.info(f"向 {url} 发送 ASR 请求，参数：{params}（超时：{asr_timeout_seconds}秒）")
                            response = client.post(url, params=params, files=files, timeout=timeout)
                            _logger.info(f"ASR 请求完成，状态码：{response.status_code}")
                            response.raise_for_status()
                            
                            # 解析 ASR 的 JSON 响应
                            asr_response_data = response.json()
                    
                    # 请求成功，退出重试循环
                    break
                    
                except httpx.HTTPStatusError as e:
                    # 检查是否为 500 错误且尚未尝试过转换
                    if e.response.status_code == 500 and attempt == 0 and not wav_conversion_attempted:
                        _logger.warning(f"录音 {recording_id} ASR 返回 500 错误，尝试高质量 MP3 转换并重试...")
                        
                        # 转换为高质量 MP3 以获得更好的兼容性
                        filename_lower = actual_filename.lower()
                        if not filename_lower.endswith('.mp3'):
                            try:
                                base_filepath, file_ext = os.path.splitext(actual_filepath)
                                temp_mp3_filepath = f"{base_filepath}_temp.mp3"
                                
                                _logger.info(f"正在将 {actual_filename} 转换为高质量 MP3 格式以便重试...")
                                subprocess.run(
                                    ['ffmpeg', '-i', actual_filepath, '-y', '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', temp_mp3_filepath],
                                    check=True, capture_output=True, text=True
                                )
                                _logger.info(f"成功将 {actual_filepath} 转换为 {temp_mp3_filepath}")
                                
                                wav_converted_filepath = temp_mp3_filepath
                                wav_conversion_attempted = True
                                continue
                            except subprocess.CalledProcessError as conv_error:
                                _logger.error(f"WAV 转换失败：{conv_error}")
                                raise e
                        else:
                            _logger.error(f"文件已经是 WAV 格式但仍收到 500 错误")
                            raise e
                    else:
                        raise e
            
            # 调试：保留转换后的文件用于质量检查
            if wav_converted_filepath and os.path.exists(wav_converted_filepath):
                try:
                    converted_size = os.path.getsize(wav_converted_filepath)
                    converted_size_mb = converted_size / (1024 * 1024)
                    
                    debug_dir = os.path.join(UPLOAD_FOLDER, 'debug_converted')
                    os.makedirs(debug_dir, exist_ok=True)
                    
                    from shutil import copy2
                    file_ext = os.path.splitext(wav_converted_filepath)[1] or '.mp3'
                    debug_filename = f"debug_{recording_id}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}{file_ext}"
                    debug_filepath = os.path.join(debug_dir, debug_filename)
                    copy2(wav_converted_filepath, debug_filepath)
                    
                    _logger.info(f"调试：转换后的文件已保留在：{debug_filepath}")
                    _logger.info(f"调试：转换后的文件大小：{converted_size_mb:.2f} MB（{converted_size} 字节）")
                    _logger.info(f"调试：原始文件：{actual_filename}")
                    _logger.info(f"调试：录音 ID：{recording_id}")
                    _logger.info(f"调试：你可以从容器中下载此文件：{debug_filepath}")
                    
                except Exception as debug_error:
                    _logger.warning(f"调试：保留转换后的文件失败：{debug_error}")
            
            # 清理原始临时转换文件（保留调试副本）
            try:
                if wav_converted_filepath and os.path.exists(wav_converted_filepath):
                    os.remove(wav_converted_filepath)
                    _logger.info(f"已清理原始临时转换文件：{wav_converted_filepath}")
            except Exception as cleanup_error:
                _logger.warning(f"清理临时转换文件失败：{cleanup_error}")
            
            # ASR 响应的调试日志
            _logger.info(f"ASR 响应键：{list(asr_response_data.keys())}")
            
            # 记录完整的原始 JSON 响应（截断以提高可读性）
            import json as json_module
            raw_json_str = json_module.dumps(asr_response_data, indent=2)
            if len(raw_json_str) > 5000:
                _logger.info(f"原始 ASR 响应（前 5000 字符）：{raw_json_str[:5000]}...")
            else:
                _logger.info(f"原始 ASR 响应：{raw_json_str}")
            
            if 'segments' in asr_response_data:
                _logger.info(f"片段数量：{len(asr_response_data['segments'])}")
                
                # 从响应中收集所有唯一说话人
                all_speakers = set()
                segments_with_speakers = 0
                segments_without_speakers = 0
                
                for segment in asr_response_data['segments']:
                    if 'speaker' in segment and segment['speaker'] is not None:
                        all_speakers.add(segment['speaker'])
                        segments_with_speakers += 1
                    else:
                        segments_without_speakers += 1
                
                _logger.info(f"原始响应中找到的唯一说话人：{sorted(list(all_speakers))}")
                _logger.info(f"有说话人的片段：{segments_with_speakers}，无说话人的片段：{segments_without_speakers}")
                
                # 记录前几个片段用于调试
                for i, segment in enumerate(asr_response_data['segments'][:5]):
                    segment_keys = list(segment.keys())
                    _logger.info(f"片段 {i} 的键：{segment_keys}")
                    _logger.info(f"片段 {i}：说话人='{segment.get('speaker')}'，文本='{segment.get('text', '')[:50]}...'")
            
            # 简化 JSON 数据
            simplified_segments = []
            if 'segments' in asr_response_data and isinstance(asr_response_data['segments'], list):
                last_known_speaker = None
                
                for i, segment in enumerate(asr_response_data['segments']):
                    speaker = segment.get('speaker')
                    text = segment.get('text', '').strip()
                    
                    # 如果片段没有说话人，使用前一个片段的说话人
                    if speaker is None:
                        if last_known_speaker is not None:
                            speaker = last_known_speaker
                            _logger.info(f"为片段 {i} 分配了前一片段的说话人 '{speaker}'")
                        else:
                            speaker = 'UNKNOWN_SPEAKER'
                            _logger.warning(f"片段 {i} 无前一个说话人可用，使用 UNKNOWN_SPEAKER")
                    else:
                        last_known_speaker = speaker
                    
                    simplified_segments.append({
                        'speaker': speaker,
                        'sentence': text,
                        'start_time': segment.get('start'),
                        'end_time': segment.get('end')
                    })
            
            # 记录最终简化片段数量
            _logger.info(f"创建了 {len(simplified_segments)} 个简化片段")
            null_speaker_count = sum(1 for seg in simplified_segments if seg['speaker'] is None)
            if null_speaker_count > 0:
                _logger.warning(f"最终输出中发现 {null_speaker_count} 个片段的说话人为空")
            
            # 将简化后的 JSON 存储为字符串
            recording.transcription = json.dumps(simplified_segments)
            
            # 提交转录数据
            db.session.commit()
            _logger.info(f"录音 {recording_id} 的 ASR 转录已完成。")
            
            # 立即生成标题
            generate_title_task(app_context, recording_id)
            
            # 始终自动为所有录音生成摘要
            _logger.info(f"正在为录音 {recording_id} 自动生成摘要")
            generate_summary_only_task(app_context, recording_id)

        except Exception as e:
            db.session.rollback()
            
            # 专门处理超时错误
            error_msg = str(e)
            if "timed out" in error_msg.lower() or "timeout" in error_msg.lower():
                asr_timeout = SystemSetting.get_setting('asr_timeout_seconds', 18000)
                _logger.error(f"录音 {recording_id} 的 ASR 处理在 {asr_timeout} 秒后超时。考虑在管理后台 > 系统设置中增加 'asr_timeout_seconds'。")
                user_error_msg = f"ASR 处理在 {asr_timeout} 秒后超时。文件可能过长，超出当前超时设置。"
            else:
                _logger.error(f"录音 {recording_id} 的 ASR 处理失败：{error_msg}")
                user_error_msg = f"ASR 处理失败：{error_msg}"
            
            recording = db.session.get(Recording, recording_id)
            if recording:
                recording.status = 'FAILED'
                recording.transcription = user_error_msg
                db.session.commit()


def transcribe_audio_task(app_context, recording_id, filepath, filename_for_asr, start_time,
                          language=None, min_speakers=None, max_speakers=None, tag_id=None,
                          ASR_BASE_URL=None, USE_ASR_ENDPOINT=True, ASR_DIARIZE=False,
                          chunking_service=None, ENABLE_CHUNKING=False,
                          transcription_api_key=None, transcription_base_url=None,
                          http_client_no_proxy=None, http_client=None,
                          generate_title_task=None, generate_summary_only_task=None,
                          app_logger=None, UPLOAD_FOLDER=None):
    """在后台线程中运行转录和摘要生成
    
    Args:
        app_context: Flask 应用上下文
        recording_id: 待处理的录音 ID
        filepath: 音频文件路径
        filename_for_asr: ASR 使用的文件名
        start_time: 处理开始时间
        language: 可选的语言代码（来自上传表单）
        min_speakers: 可选的最少说话人数（来自上传表单）
        max_speakers: 可选的最多说话人数（来自上传表单）
        tag_id: 可选的标签 ID，用于应用自定义提示词
    """
    _logger = app_logger or logger
    
    if USE_ASR_ENDPOINT:
        with app_context:
            recording = db.session.get(Recording, recording_id)
            # 使用 ASR 配置的说话人分离设置（已从 config 统一读取）
            diarize_setting = ASR_DIARIZE
            
            # 如果上传表单提供了语言则使用，否则使用用户默认设置
            if language:
                user_transcription_language = language
            else:
                user_transcription_language = recording.owner.transcription_language if recording.owner else None
        # 使用上传表单中的最少数/最多数说话人设置（已处理优先级层次）
        final_min_speakers = min_speakers
        final_max_speakers = max_speakers
        
        transcribe_audio_asr(app_context, recording_id, filepath, filename_for_asr, start_time, 
                           mime_type=recording.mime_type, 
                           language=user_transcription_language, 
                           diarize=diarize_setting,
                           min_speakers=final_min_speakers,
                           max_speakers=final_max_speakers,
                           tag_id=tag_id,
                           ASR_BASE_URL=ASR_BASE_URL, USE_ASR_ENDPOINT=USE_ASR_ENDPOINT,
                           ASR_DIARIZE=ASR_DIARIZE,
                           generate_title_task=generate_title_task,
                           generate_summary_only_task=generate_summary_only_task,
                           app_logger=_logger, UPLOAD_FOLDER=UPLOAD_FOLDER)
        
        # ASR 任务完成后，计算处理时间
        with app_context:
            recording = db.session.get(Recording, recording_id)
            if recording.status in ['COMPLETED', 'FAILED']:
                end_time = datetime.utcnow()
                recording.processing_time_seconds = (end_time - start_time).total_seconds()
                db.session.commit()
        return
    
    with app_context:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            _logger.error(f"错误：未找到录音 {recording_id}，无法进行转录。")
            return

        try:
            _logger.info(f"开始转录录音 {recording_id}（{filename_for_asr}）...")
            recording.status = 'PROCESSING'
            db.session.commit()

            # 检查大文件是否需要分块处理
            needs_chunking = (chunking_service and 
                            ENABLE_CHUNKING and 
                            chunking_service.needs_chunking(filepath, USE_ASR_ENDPOINT))
            
            if needs_chunking:
                _logger.info(f"文件 {filepath} 较大（{os.path.getsize(filepath)/1024/1024:.1f}MB），使用分块转录")
                transcription_text = transcribe_with_chunking(app_context, recording_id, filepath, filename_for_asr,
                                                              db=db, Recording=Recording,
                                                              chunking_service=chunking_service,
                                                              transcription_api_key=transcription_api_key,
                                                              transcription_base_url=transcription_base_url,
                                                              http_client_no_proxy=http_client_no_proxy,
                                                              app_logger=_logger)
            else:
                # --- 小文件的标准转录 ---
                transcription_text = transcribe_single_file(filepath, recording,
                                                            db=db,
                                                            transcription_api_key=transcription_api_key,
                                                            transcription_base_url=transcription_base_url,
                                                            http_client_no_proxy=http_client_no_proxy,
                                                            app_logger=_logger)
            
            recording.transcription = transcription_text
            _logger.info(f"录音 {recording_id} 转录完成。文本长度：{len(recording.transcription)}")
            
            # 立即生成标题
            generate_title_task(app_context, recording_id)
            
            # 始终自动为所有录音生成摘要
            _logger.info(f"正在为录音 {recording_id} 自动生成摘要")
            generate_summary_only_task(app_context, recording_id)

        except Exception as e:
            db.session.rollback()
            _logger.error(f"录音 {recording_id} 处理失败：{str(e)}", exc_info=True)
            # 重新获取录音（会话可能已被回滚）
            recording = db.session.get(Recording, recording_id)
            if recording:
                if recording.status not in ['COMPLETED', 'FAILED']:
                    recording.status = 'FAILED'
                if not recording.transcription:
                    recording.transcription = f"处理失败：{str(e)}"
                if recording.status == 'SUMMARIZING' and not recording.summary:
                    recording.summary = f"[摘要生成期间处理失败：{str(e)}]"
                db.session.commit()
        
        # 计算处理时间
        end_time = datetime.utcnow()
        recording = db.session.get(Recording, recording_id)
        if recording and recording.status in ['COMPLETED', 'FAILED']:
            recording.processing_time_seconds = (end_time - start_time).total_seconds()
            db.session.commit()


def transcribe_single_file(filepath, recording, db=None,
                           transcription_api_key=None, transcription_base_url=None,
                           http_client_no_proxy=None, app_logger=None):
    """使用 OpenAI Whisper API 转录单个音频文件"""
    _logger = app_logger or logger
    
    # 如果未提供，使用配置的默认值
    if transcription_api_key is None:
        transcription_api_key = TRANSCRIPTION_API_KEY
    if transcription_base_url is None:
        transcription_base_url = TRANSCRIPTION_BASE_URL
    
    # 检查是否需要从视频容器中提取音频
    actual_filepath = filepath
    mime_type = recording.mime_type if recording else None
    
    # 检测视频容器
    is_video = False
    if mime_type:
        is_video = mime_type.startswith('video/')
    else:
        is_video = filepath.lower().endswith(('.mp4', '.mov', '.avi', '.mkv', '.webm', '.wmv', '.3gp'))
    
    if is_video:
        _logger.info(f"检测到视频容器用于 Whisper 转录，正在提取音频...")
        try:
            audio_filepath, audio_mime_type = extract_audio_from_video(filepath, 'wav', app_logger=_logger)
            actual_filepath = audio_filepath
            
            if recording:
                recording.audio_path = audio_filepath
                recording.mime_type = audio_mime_type
                db.session.commit()
            
            _logger.info(f"Whisper 音频提取成功：{audio_filepath}")
        except Exception as e:
            _logger.error(f"Whisper 从视频中提取音频失败：{str(e)}")
            if recording:
                recording.status = 'FAILED'
                recording.error_message = f"音频提取失败：{str(e)}"
                db.session.commit()
            raise Exception(f"音频提取失败：{str(e)}")
    
    # Whisper API 支持的文件格式列表
    WHISPER_SUPPORTED_FORMATS = ['flac', 'm4a', 'mp3', 'mp4', 'mpeg', 'mpga', 'oga', 'ogg', 'wav', 'webm']
    
    # 检查文件格式是否需要转换
    file_ext = os.path.splitext(actual_filepath)[1].lower().lstrip('.')
    converted_filepath = None
    
    # 延迟导入 http_client，避免循环导入
    from src.clients.llm_client import http_client_no_proxy as llm_http_client
    
    try:
        with open(actual_filepath, 'rb') as audio_file:
            transcription_client = OpenAI(
                api_key=transcription_api_key,
                base_url=transcription_base_url,
                http_client=http_client_no_proxy or llm_http_client
            )
            whisper_model = os.environ.get("WHISPER_MODEL", "Systran/faster-distil-whisper-large-v3")
            
            user_transcription_language = None
            if recording and recording.owner:
                user_transcription_language = recording.owner.transcription_language
            
            transcription_language = user_transcription_language

            transcription_params = {
                "model": whisper_model,
                "file": audio_file
            }

            if transcription_language:
                transcription_params["language"] = transcription_language
                _logger.info(f"使用转录语言：{transcription_language}")
            else:
                _logger.info("未设置转录语言，使用自动检测或服务默认值。")

            transcript = transcription_client.audio.transcriptions.create(**transcription_params)
            return transcript.text
            
    except Exception as e:
        # 检查是否为格式错误
        error_message = str(e)
        if "Invalid file format" in error_message or "Supported formats" in error_message:
            _logger.warning(f"检测到不支持的音频格式 '{file_ext}'，正在转换为 MP3...")
            
            # 转换为 MP3
            temp_mp3_filepath = None
            try:
                with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as temp_mp3:
                    temp_mp3_filepath = temp_mp3.name
                
                # 使用 ffmpeg 以一致的设置转换为 MP3
                subprocess.run(
                    ['ffmpeg', '-i', actual_filepath, '-y', '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', temp_mp3_filepath],
                    check=True,
                    capture_output=True
                )
                _logger.info(f"成功将 {actual_filepath} 转换为 MP3 格式")
                converted_filepath = temp_mp3_filepath
                
                # 使用转换后的文件重试转录
                with open(converted_filepath, 'rb') as audio_file:
                    transcription_client = OpenAI(
                        api_key=transcription_api_key,
                        base_url=transcription_base_url,
                        http_client=http_client_no_proxy or llm_http_client
                    )
                    
                    transcription_params = {
                        "model": whisper_model,
                        "file": audio_file
                    }

                    if transcription_language:
                        transcription_params["language"] = transcription_language

                    transcript = transcription_client.audio.transcriptions.create(**transcription_params)
                    return transcript.text
                    
            finally:
                # 清理临时转换文件
                if converted_filepath and os.path.exists(converted_filepath):
                    try:
                        os.unlink(converted_filepath)
                        _logger.info(f"已清理临时转换文件：{converted_filepath}")
                    except Exception as cleanup_error:
                        _logger.warning(f"清理临时文件 {converted_filepath} 失败：{cleanup_error}")
        else:
            # 如果不是格式错误则重新抛出
            raise


def transcribe_with_chunking(app_context, recording_id, filepath, filename_for_asr,
                             db=None, Recording=None,
                             chunking_service=None,
                             transcription_api_key=None, transcription_base_url=None,
                             http_client_no_proxy=None, app_logger=None):
    """使用分块方式转录大型音频文件"""
    _logger = app_logger or logger
    import tempfile
    
    # 如果未提供，使用配置的默认值
    if transcription_api_key is None:
        transcription_api_key = TRANSCRIPTION_API_KEY
    if transcription_base_url is None:
        transcription_base_url = TRANSCRIPTION_BASE_URL
    
    with app_context:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            raise ValueError(f"未找到录音 {recording_id}")
    
    # 为分块创建临时目录
    with tempfile.TemporaryDirectory() as temp_dir:
        try:
            # 创建分块
            _logger.info(f"正在为大文件创建分块：{filepath}")
            chunks = chunking_service.create_chunks(filepath, temp_dir)
            
            if not chunks:
                from src.services.audio_chunking import ChunkProcessingError
                raise ChunkProcessingError("未从音频文件创建任何分块")
            
            _logger.info(f"已创建 {len(chunks)} 个分块，正在使用 Whisper API 处理每个分块...")
            
            # 使用适当的超时和重试处理处理每个分块
            chunk_results = []
            
            # 创建具有适当超时的 HTTP 客户端
            timeout_config = httpx.Timeout(
                connect=30.0,
                read=300.0,
                write=60.0,
                pool=10.0
            )
            
            http_client_with_timeout = httpx.Client(
                verify=True,
                timeout=timeout_config,
                limits=httpx.Limits(max_connections=5, max_keepalive_connections=2)
            )
            
            transcription_client = OpenAI(
                api_key=transcription_api_key,
                base_url=transcription_base_url,
                http_client=http_client_with_timeout,
                max_retries=3,
                timeout=300.0
            )
            whisper_model = os.environ.get("WHISPER_MODEL", "Systran/faster-distil-whisper-large-v3")
            
            # 获取用户语言偏好
            user_transcription_language = None
            with app_context:
                recording = db.session.get(Recording, recording_id)
                if recording and recording.owner:
                    user_transcription_language = recording.owner.transcription_language
            
            for i, chunk in enumerate(chunks):
                max_chunk_retries = 3
                chunk_retry_count = 0
                chunk_success = False
                
                while chunk_retry_count < max_chunk_retries and not chunk_success:
                    try:
                        retry_suffix = f"（重试 {chunk_retry_count + 1}/{max_chunk_retries}）" if chunk_retry_count > 0 else ""
                        _logger.info(f"正在处理分块 {i+1}/{len(chunks)}：{chunk['filename']}（{chunk['size_mb']:.1f}MB）{retry_suffix}")
                        
                        # 记录每个步骤的详细耗时
                        step_start_time = time.time()
                        
                        # 步骤 1：打开文件
                        file_open_start = time.time()
                        with open(chunk['path'], 'rb') as chunk_file:
                            file_open_time = time.time() - file_open_start
                            _logger.info(f"分块 {i+1}：文件打开耗时 {file_open_time:.2f}秒")
                            
                            # 步骤 2：准备转录参数
                            param_start = time.time()
                            transcription_params = {
                                "model": whisper_model,
                                "file": chunk_file
                            }
                            
                            if user_transcription_language:
                                transcription_params["language"] = user_transcription_language
                            
                            param_time = time.time() - param_start
                            _logger.info(f"分块 {i+1}：参数准备耗时 {param_time:.2f}秒")
                            
                            # 步骤 3：API 调用及详细计时
                            api_start = time.time()
                            _logger.info(f"分块 {i+1}：开始向 {transcription_base_url} 发起 API 调用")
                            
                            # 记录连接详情
                            _logger.info(f"分块 {i+1}：超时配置 - 连接：30秒，读取：300秒，写入：60秒")
                            _logger.info(f"分块 {i+1}：最大重试：2次，API 超时：300秒")
                            
                            try:
                                transcript = transcription_client.audio.transcriptions.create(**transcription_params)
                            except Exception as chunk_error:
                                # 检查是否为格式错误
                                error_msg = str(chunk_error)
                                if "Invalid file format" in error_msg or "Supported formats" in error_msg:
                                    _logger.warning(f"分块 {i+1} 格式问题，尝试转换...")
                                    with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as temp_mp3:
                                        temp_mp3_path = temp_mp3.name
                                    try:
                                        subprocess.run(
                                            ['ffmpeg', '-i', chunk['path'], '-y', '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', temp_mp3_path],
                                            check=True,
                                            capture_output=True
                                        )
                                        with open(temp_mp3_path, 'rb') as converted_chunk:
                                            transcription_params['file'] = converted_chunk
                                            transcript = transcription_client.audio.transcriptions.create(**transcription_params)
                                    finally:
                                        if os.path.exists(temp_mp3_path):
                                            os.unlink(temp_mp3_path)
                                else:
                                    raise
                            
                            api_time = time.time() - api_start
                            _logger.info(f"分块 {i+1}：API 调用完成，耗时 {api_time:.2f}秒")
                            
                            # 步骤 4：处理响应
                            response_start = time.time()
                            chunk_result = {
                                'index': chunk['index'],
                                'start_time': chunk['start_time'],
                                'end_time': chunk['end_time'],
                                'duration': chunk['duration'],
                                'size_mb': chunk['size_mb'],
                                'transcription': transcript.text,
                                'filename': chunk['filename'],
                                'processing_time': api_time
                            }
                            chunk_results.append(chunk_result)
                            response_time = time.time() - response_start
                            
                            total_time = time.time() - step_start_time
                            _logger.info(f"分块 {i+1}：响应处理耗时 {response_time:.2f}秒")
                            _logger.info(f"分块 {i+1}：总处理耗时 {total_time:.2f}秒")
                            _logger.info(f"分块 {i+1} 转录成功：{len(transcript.text)} 字符")
                            chunk_success = True
                            
                    except Exception as chunk_error:
                        chunk_retry_count += 1
                        error_msg = str(chunk_error)
                        
                        if chunk_retry_count < max_chunk_retries:
                            # 根据错误类型确定等待时间
                            if "timeout" in error_msg.lower() or "timed out" in error_msg.lower():
                                wait_time = 30
                            elif "rate limit" in error_msg.lower():
                                wait_time = 60
                            else:
                                wait_time = 15
                            
                            _logger.warning(f"分块 {i+1} 失败（第 {chunk_retry_count}/{max_chunk_retries} 次尝试）：{chunk_error}。{wait_time}秒后重试...")
                            time.sleep(wait_time)
                        else:
                            _logger.error(f"分块 {i+1} 在 {max_chunk_retries} 次尝试后仍然失败：{chunk_error}")
                            # 将失败的分块添加到结果中
                            chunk_result = {
                                'index': chunk['index'],
                                'start_time': chunk['start_time'],
                                'end_time': chunk['end_time'],
                                'transcription': f"[分块 {i+1} 转录在 {max_chunk_retries} 次尝试后失败：{str(chunk_error)}]",
                                'filename': chunk['filename']
                            }
                            chunk_results.append(chunk_result)
                
                # 分块之间添加短暂延迟，避免对 API 造成过大压力
                if i < len(chunks) - 1:
                    time.sleep(2)
            
            # 合并转录结果
            _logger.info(f"正在合并 {len(chunk_results)} 个分块的转录结果...")
            merged_transcription = chunking_service.merge_transcriptions(chunk_results)
            
            if not merged_transcription.strip():
                from src.services.audio_chunking import ChunkProcessingError
                raise ChunkProcessingError("合并后的转录结果为空")
            
            # 记录详细的性能统计和分析
            chunking_service.log_processing_statistics(chunk_results)
            
            # 获取性能建议
            recommendations = chunking_service.get_performance_recommendations(chunk_results)
            if recommendations:
                _logger.info("=== 性能建议 ===")
                for i, rec in enumerate(recommendations, 1):
                    _logger.info(f"{i}. {rec}")
                _logger.info("=== 建议结束 ===")
            
            _logger.info(f"分块转录完成。最终长度：{len(merged_transcription)} 字符")
            return merged_transcription
            
        except Exception as e:
            _logger.error(f"分块转录失败，文件 {filepath}：{e}")
            # 清理分块（如果存在）
            if 'chunks' in locals():
                chunking_service.cleanup_chunks(chunks)
            from src.services.audio_chunking import ChunkProcessingError
            raise ChunkProcessingError(f"分块转录失败：{str(e)}")
        finally:
            # 清理工作由 tempfile.TemporaryDirectory 上下文管理器处理
            pass
