# 录音管理蓝图
# 处理录音的上传、查询、下载、更新、删除、重新处理等功能

from flask import Blueprint, request, jsonify, send_file
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
from datetime import datetime, timedelta
from sqlalchemy import select, func
import os
import json
import re
import subprocess
import mimetypes
import threading
import shutil

from src.clients import db
from src.models import (
    Recording, Tag, RecordingTag, Event, TranscriptChunk,
    SystemSetting, SystemConfig
)
from src.services import (
    # 转录服务
    transcribe_audio_asr,
    transcribe_audio_task,
    # 摘要服务
    generate_title_task,
    generate_summary_only_task,
    # 说话人服务
    update_speaker_usage,
    identify_unidentified_speakers_from_text,
    # LLM 服务
    format_transcription_for_llm,
    call_llm_completion,
    extract_corrected_text,
)
from src.utils.standard_word_mappings import apply_standard_word_mappings_with_details

def _normalize_asr_scene(scene):
    return 'yl' if str(scene or '').strip().lower() == 'yl' else 'cg'


def _standard_word_mappings_key_for_scene(scene):
    return 'standard_word_mappings_yl' if _normalize_asr_scene(scene) == 'yl' else 'standard_word_mappings'

# 创建录音蓝图
recording_bp = Blueprint('recording', __name__)

# 全局变量(从 app.py 导入)
limiter = None
csrf = None
bcrypt = None
app_logger = None
flask_app = None
client = None
USE_ASR_ENDPOINT = None
ASR_BASE_URL = None
ASR_DIARIZE = None
ASR_MIN_SPEAKERS = None
ASR_MAX_SPEAKERS = None
ENABLE_CHUNKING = None
chunking_service = None
ENABLE_INQUIRE_MODE = None

def init_recording_bp(
    limiter_instance, 
    csrf_instance, 
    bcrypt_instance, 
    logger,
    flask_app_instance,
    openai_client,
    use_asr_endpoint,
    asr_base_url,
    asr_diarize,
    asr_min_speakers,
    asr_max_speakers,
    enable_chunking,
    chunking_svc,
    enable_inquire_mode
):
    """初始化蓝图所需的全局变量"""
    global limiter, csrf, bcrypt, app_logger, flask_app, client
    global USE_ASR_ENDPOINT, ASR_BASE_URL, ASR_DIARIZE, ASR_MIN_SPEAKERS, ASR_MAX_SPEAKERS
    global ENABLE_CHUNKING, chunking_service, ENABLE_INQUIRE_MODE
    
    limiter = limiter_instance
    csrf = csrf_instance
    bcrypt = bcrypt_instance
    app_logger = logger
    flask_app = flask_app_instance
    client = openai_client
    USE_ASR_ENDPOINT = use_asr_endpoint
    ASR_BASE_URL = asr_base_url
    ASR_DIARIZE = asr_diarize
    ASR_MIN_SPEAKERS = asr_min_speakers
    ASR_MAX_SPEAKERS = asr_max_speakers
    ENABLE_CHUNKING = enable_chunking
    chunking_service = chunking_svc
    ENABLE_INQUIRE_MODE = enable_inquire_mode

    # 应用路由级限流（在 init 中应用，避免模块导入时 limiter 为 None）
    # 录音列表限流：每分钟最多30次
    recording_bp.view_functions['get_recordings_paginated'] = limiter.limit("30 per minute")(get_recordings_paginated)
    # 状态查询限流：每分钟最多60次（轮询接口）
    recording_bp.view_functions['get_status'] = limiter.limit("60 per minute")(get_status)
    # 上传限流：每分钟最多10次
    recording_bp.view_functions['upload_file'] = limiter.limit("10 per minute")(upload_file)


def create_recording_from_existing_file(
    filepath,
    original_filename,
    notes=None,
    selected_tags=None,
    language='',
    min_speakers=None,
    max_speakers=None,
    mime_type=None,
    processing_source='upload'
):
    selected_tags = selected_tags or []
    final_file_size = os.path.getsize(filepath)
    if not mime_type:
        mime_type, _ = mimetypes.guess_type(filepath)

    now = datetime.utcnow()
    recording = Recording(
        audio_path=filepath,
        original_filename=original_filename,
        title=f"Recording - {original_filename}",
        file_size=final_file_size,
        status='PENDING',
        meeting_date=now.date(),
        user_id=current_user.id,
        mime_type=mime_type,
        notes=notes,
        processing_source=processing_source
    )
    db.session.add(recording)
    db.session.commit()

    for order, tag in enumerate(selected_tags, 1):
        db.session.add(RecordingTag(
            recording_id=recording.id,
            tag_id=tag.id,
            order=order,
            added_at=datetime.utcnow()
        ))

    if selected_tags:
        db.session.commit()
        tag_names = [tag.name for tag in selected_tags]
        app_logger.info(f"Added {len(selected_tags)} tags to recording {recording.id}: {', '.join(tag_names)}")

    app_logger.info(f"Created recording {recording.id} from {processing_source}")

    start_time = datetime.utcnow()
    first_tag = selected_tags[0] if selected_tags else None
    task_kwargs = {
        'tag_id': first_tag.id if first_tag else None,
        'USE_ASR_ENDPOINT': USE_ASR_ENDPOINT,
        'generate_title_task': generate_title_task,
        'generate_summary_only_task': generate_summary_only_task,
        'app_logger': app_logger,
        'UPLOAD_FOLDER': flask_app.config.get('UPLOAD_FOLDER')
    }

    if USE_ASR_ENDPOINT:
        task_kwargs.update({
            'language': language,
            'min_speakers': min_speakers,
            'max_speakers': max_speakers,
            'ASR_BASE_URL': ASR_BASE_URL,
            'ASR_DIARIZE': ASR_DIARIZE,
        })

    thread = threading.Thread(
        target=transcribe_audio_task,
        args=(flask_app.app_context(), recording.id, filepath, os.path.basename(filepath), start_time),
        kwargs=task_kwargs
    )
    thread.start()
    app_logger.info(f"Started background processing thread for recording {recording.id}")

    return recording


# ==================== 录音查询相关路由 ====================

@recording_bp.route('/recordings', methods=['GET'])
def get_recordings():
    """获取所有录音(未分页)"""
    try:
        # 检查用户是否已登录
        if not current_user.is_authenticated:
            return jsonify([])  # 如果未登录返回空数组
            
        # 按当前用户筛选录音
        stmt = select(Recording).where(Recording.user_id == current_user.id).order_by(Recording.created_at.desc())
        recordings = db.session.execute(stmt).scalars().all()
        return jsonify([recording.to_dict() for recording in recordings])
    except Exception as e:
        app_logger.error(f"获取录音时出错: {e}")
        return jsonify({'error': str(e)}), 500


@recording_bp.route('/api/recordings', methods=['GET'])
@login_required
def get_recordings_paginated():
    """获取录音(带分页和服务器端过滤)"""
    try:
        # 解析查询参数
        page = request.args.get('page', 1, type=int)
        per_page = min(request.args.get('per_page', 25, type=int), 100)  # 每页最多 100 条
        search_query = request.args.get('q', '').strip()
        
        # 构建基础查询
        stmt = select(Recording).where(Recording.user_id == current_user.id)
        
        # 如果提供了搜索查询,应用过滤
        if search_query:
            # 解析搜索查询的特殊语法
            # 提取日期过滤
            date_filters = re.findall(r'date:(\S+)', search_query.lower())
            date_from_filters = re.findall(r'date_from:(\S+)', search_query.lower())
            date_to_filters = re.findall(r'date_to:(\S+)', search_query.lower())
            tag_filters = re.findall(r'tag:(\S+)', search_query.lower())
            
            # 移除特殊语法以获取文本搜索
            text_query = re.sub(r'date:\S+', '', search_query, flags=re.IGNORECASE)
            text_query = re.sub(r'date_from:\S+', '', text_query, flags=re.IGNORECASE)
            text_query = re.sub(r'date_to:\S+', '', text_query, flags=re.IGNORECASE)
            text_query = re.sub(r'tag:\S+', '', text_query, flags=re.IGNORECASE).strip()
            
            # 应用日期过滤
            for date_filter in date_filters:
                if date_filter == 'today':
                    today = datetime.now().date()
                    stmt = stmt.where(
                        db.or_(
                            db.func.date(Recording.meeting_date) == today,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) == today
                            )
                        )
                    )
                elif date_filter == 'yesterday':
                    yesterday = datetime.now().date() - timedelta(days=1)
                    stmt = stmt.where(
                        db.or_(
                            db.func.date(Recording.meeting_date) == yesterday,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) == yesterday
                            )
                        )
                    )
                elif date_filter == 'thisweek':
                    today = datetime.now().date()
                    start_of_week = today - timedelta(days=today.weekday())
                    stmt = stmt.where(
                        db.or_(
                            Recording.meeting_date >= start_of_week,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) >= start_of_week
                            )
                        )
                    )
                elif date_filter == 'lastweek':
                    today = datetime.now().date()
                    end_of_last_week = today - timedelta(days=today.weekday())
                    start_of_last_week = end_of_last_week - timedelta(days=7)
                    stmt = stmt.where(
                        db.or_(
                            db.and_(
                                Recording.meeting_date >= start_of_last_week,
                                Recording.meeting_date < end_of_last_week
                            ),
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) >= start_of_last_week,
                                db.func.date(Recording.created_at) < end_of_last_week
                            )
                        )
                    )
                elif date_filter == 'thismonth':
                    today = datetime.now().date()
                    start_of_month = today.replace(day=1)
                    stmt = stmt.where(
                        db.or_(
                            Recording.meeting_date >= start_of_month,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) >= start_of_month
                            )
                        )
                    )
                elif date_filter == 'lastmonth':
                    today = datetime.now().date()
                    first_day_this_month = today.replace(day=1)
                    last_day_last_month = first_day_this_month - timedelta(days=1)
                    first_day_last_month = last_day_last_month.replace(day=1)
                    stmt = stmt.where(
                        db.or_(
                            db.and_(
                                Recording.meeting_date >= first_day_last_month,
                                Recording.meeting_date <= last_day_last_month
                            ),
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) >= first_day_last_month,
                                db.func.date(Recording.created_at) <= last_day_last_month
                            )
                        )
                    )
                elif re.match(r'^\d{4}-\d{2}-\d{2}$', date_filter):
                    # 特定日期格式 YYYY-MM-DD
                    target_date = datetime.strptime(date_filter, '%Y-%m-%d').date()
                    stmt = stmt.where(
                        db.or_(
                            db.func.date(Recording.meeting_date) == target_date,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) == target_date
                            )
                        )
                    )
                elif re.match(r'^\d{4}-\d{2}$', date_filter):
                    # 月份格式 YYYY-MM
                    year, month = map(int, date_filter.split('-'))
                    stmt = stmt.where(
                        db.or_(
                            db.and_(
                                db.extract('year', Recording.meeting_date) == year,
                                db.extract('month', Recording.meeting_date) == month
                            ),
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.extract('year', Recording.created_at) == year,
                                db.extract('month', Recording.created_at) == month
                            )
                        )
                    )
                elif re.match(r'^\d{4}$', date_filter):
                    # 年份格式 YYYY
                    year = int(date_filter)
                    stmt = stmt.where(
                        db.or_(
                            db.extract('year', Recording.meeting_date) == year,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.extract('year', Recording.created_at) == year
                            )
                        )
                    )
            
            # 应用日期范围过滤
            if date_from_filters and date_from_filters[0]:
                try:
                    date_from = datetime.strptime(date_from_filters[0], '%Y-%m-%d').date()
                    stmt = stmt.where(
                        db.or_(
                            Recording.meeting_date >= date_from,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) >= date_from
                            )
                        )
                    )
                except ValueError:
                    pass  # 无效的日期格式,忽略
            
            if date_to_filters and date_to_filters[0]:
                try:
                    date_to = datetime.strptime(date_to_filters[0], '%Y-%m-%d').date()
                    stmt = stmt.where(
                        db.or_(
                            Recording.meeting_date <= date_to,
                            db.and_(
                                Recording.meeting_date.is_(None),
                                db.func.date(Recording.created_at) <= date_to
                            )
                        )
                    )
                except ValueError:
                    pass  # 无效的日期格式,忽略
            
            # 应用标签过滤
            if tag_filters:
                # 与标签表连接并按标签名称过滤
                tag_conditions = []
                for tag_filter in tag_filters:
                    # 将下划线替换回空格以进行匹配
                    tag_name = tag_filter.replace('_', ' ')
                    tag_conditions.append(Tag.name.ilike(f'%{tag_name}%'))
                
                stmt = stmt.join(RecordingTag).join(Tag).where(db.or_(*tag_conditions))
            
            # 应用文本搜索
            if text_query:
                text_conditions = [
                    Recording.title.ilike(f'%{text_query}%'),
                    Recording.participants.ilike(f'%{text_query}%'),
                    Recording.transcription.ilike(f'%{text_query}%'),
                    Recording.notes.ilike(f'%{text_query}%')
                ]
                stmt = stmt.where(db.or_(*text_conditions))
        
        # 应用排序(基于 meeting_date 或 created_at 的最新优先)
        stmt = stmt.order_by(
            db.case(
                (Recording.meeting_date.is_not(None), Recording.meeting_date),
                else_=db.func.date(Recording.created_at)
            ).desc(),
            Recording.created_at.desc()
        )
        
        # 获取总数用于分页信息
        count_stmt = select(db.func.count()).select_from(stmt.subquery())
        total_count = db.session.execute(count_stmt).scalar()
        
        # 应用分页
        offset = (page - 1) * per_page
        stmt = stmt.offset(offset).limit(per_page)
        
        # 执行查询
        recordings = db.session.execute(stmt).scalars().all()
        
        # 计算分页元数据
        total_pages = (total_count + per_page - 1) // per_page
        has_next = page < total_pages
        has_prev = page > 1
        
        return jsonify({
            'recordings': [recording.to_dict() for recording in recordings],
            'pagination': {
                'page': page,
                'per_page': per_page,
                'total': total_count,
                'total_pages': total_pages,
                'has_next': has_next,
                'has_prev': has_prev
            }
        })
        
    except Exception as e:
        app_logger.error(f"获取分页录音时出错: {e}")
        return jsonify({'error': str(e)}), 500


# ==================== 录音上传相关路由 ====================

@recording_bp.route('/upload', methods=['POST'])
@login_required
def upload_file():
    """上传音频文件"""
    try:
        if 'file' not in request.files:
            return jsonify({'error': '未提供文件'}), 400

        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': '未选择文件'}), 400

        original_filename = file.filename
        safe_filename = secure_filename(original_filename)
        filepath = os.path.join(flask_app.config['UPLOAD_FOLDER'], f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{safe_filename}")

        # 获取原始文件大小
        file.seek(0, os.SEEK_END)
        original_file_size = file.tell()
        file.seek(0)
        app_logger.info(f"上传文件大小: {original_file_size} 字节 ({original_file_size / (1024 * 1024):.2f} MB)")

        # 在保存前检查大小限制 - 仅在禁用分块或使用 ASR 端点时强制执行
        max_content_length = flask_app.config.get('MAX_CONTENT_LENGTH')
        
        # 如果启用了分块且使用 OpenAI Whisper API,跳过大小检查
        should_enforce_size_limit = True
        if ENABLE_CHUNKING and chunking_service and not USE_ASR_ENDPOINT:
            should_enforce_size_limit = False
            # 获取分块模式以便更好地记录日志
            mode, limit_value = chunking_service.parse_chunk_limit()
            if mode == 'size':
                app_logger.info(f"已启用基于大小的分块({limit_value}MB 限制) - 跳过 {original_file_size/1024/1024:.1f}MB 大小限制检查")
            else:
                app_logger.info(f"已启用基于时长的分块({limit_value}s 限制) - 跳过 {original_file_size/1024/1024:.1f}MB 大小限制检查")
        
        if should_enforce_size_limit and max_content_length and original_file_size > max_content_length:
            raise RequestEntityTooLarge()

        file.save(filepath)
        app_logger.info(f"文件已保存到 {filepath}")
        
        # ==================== 原始文件强制备份 ====================
        try:
            # 1. 定义备份目录(在 upload 目录下创建一个独立的 backup 文件夹)
            backup_dir = os.path.join(flask_app.config['UPLOAD_FOLDER'], 'original_backups')
            os.makedirs(backup_dir, exist_ok=True)

            # 2. 生成备份文件名(增加时间戳前缀,防止重名覆盖)
            # 注意:这里使用 copy2 保留文件元数据
            backup_filename = f"RAW_{datetime.now().strftime('%Y%m%d%H%M%S')}_{safe_filename}"
            backup_filepath = os.path.join(backup_dir, backup_filename)

            # 3. 执行物理复制
            shutil.copy2(filepath, backup_filepath)
            app_logger.info(f"【安全备份】原始文件已备份至: {backup_filepath}")

        except Exception as backup_error:
            # 即使备份出错,也只打印日志,不阻断主流程(因为主文件已经存在了)
            app_logger.error(f"【安全备份失败】无法备份原始文件: {str(backup_error)}")
        
        # --- 仅在需要分块时转换文件 ---
        filename_lower = original_filename.lower()
        
        # 检查此文件是否需要分块
        needs_chunking_for_processing = (chunking_service and 
                                       ENABLE_CHUNKING and 
                                       not USE_ASR_ENDPOINT and
                                       chunking_service.needs_chunking(filepath, USE_ASR_ENDPOINT))
        
        # 根据是否需要分块定义支持的格式
        if needs_chunking_for_processing:
            # 对于分块:仅支持与分块兼容的格式
            supported_formats = ('.wav', '.mp3', '.flac')
            convertible_formats = ('.amr', '.3gp', '.3gpp', '.m4a', '.aac', '.ogg', '.wma', '.webm')
        else:
            # 对于直接转录:直接支持 WebM 和其他格式
            supported_formats = ('.wav', '.mp3', '.flac', '.webm', '.m4a', '.aac', '.ogg')
            convertible_formats = ('.amr', '.3gp', '.3gpp', '.wma')
        
        # 使用 ASR 端点时对有问题的 AAC 文件的特殊处理
        is_problematic_aac = (USE_ASR_ENDPOINT and 
                             (filename_lower.endswith('.aac') or 
                              'aac' in filename_lower.lower()))
        
        # 如果文件不在支持的格式中,或者是有问题的 AAC 文件用于 ASR,则转换
        should_convert = ((not filename_lower.endswith(supported_formats) and needs_chunking_for_processing) or 
                         is_problematic_aac)
        
        if should_convert:
            if is_problematic_aac:
                app_logger.info(f"将 AAC 编码文件 {filename_lower} 转换为高质量 MP3 以兼容 ASR 端点")
            elif filename_lower.endswith(convertible_formats):
                app_logger.info(f"将 {filename_lower} 格式转换为高质量 MP3 以进行分块处理")
            else:
                app_logger.info(f"尝试将未知格式({filename_lower})转换为高质量 MP3 以进行分块")
            
            base_filepath, _ = os.path.splitext(filepath)
            temp_mp3_filepath = f"{base_filepath}_temp.mp3"
            mp3_filepath = f"{base_filepath}.mp3"

            try:
                # 转换为高质量 MP3(128kbps, 44.1kHz)以提高转录准确率
                subprocess.run(
                    ['ffmpeg', '-i', filepath, '-y', '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', '-ac', '1', temp_mp3_filepath],
                    check=True, capture_output=True, text=True
                )
                app_logger.info(f"成功将 {filepath} 转换为 {temp_mp3_filepath} (128kbps MP3)")
                
                # 如果原始文件与最终 mp3 文件不同,删除它
                if filepath.lower() != mp3_filepath.lower():
                    os.remove(filepath)
                
                # 将临时文件重命名为最终文件名
                os.rename(temp_mp3_filepath, mp3_filepath)
                
                filepath = mp3_filepath
            except FileNotFoundError:
                app_logger.error("未找到 ffmpeg 命令。请确保 ffmpeg 已安装并在系统 PATH 中")
                return jsonify({'error': '服务器上未找到音频转换工具(ffmpeg)'}), 500
            except subprocess.CalledProcessError as e:
                app_logger.error(f"ffmpeg 转换失败 {filepath}: {e.stderr}")
                return jsonify({'error': f'音频文件转换失败: {e.stderr}'}), 500
        elif not filename_lower.endswith(supported_formats):
            # 文件不受支持且不需要分块 - 记录日志但不转换
            app_logger.info(f"文件格式 {filename_lower} 将直接处理,无需转换(不需要分块)")

        # 获取最终文件大小(原始或转换后的文件)
        final_file_size = os.path.getsize(filepath)

        # 确定最终文件的 MIME 类型
        mime_type, _ = mimetypes.guess_type(filepath)
        app_logger.info(f"最终 MIME 类型: {mime_type} 文件 {filepath}")

        # 从表单获取笔记
        notes = request.form.get('notes')
        
        # 如果提供了选定的标签(支持多标签)
        selected_tags = []
        tag_index = 0
        while True:
            tag_id_key = f'tag_ids[{tag_index}]'
            tag_id = request.form.get(tag_id_key)
            if not tag_id:
                break
            
            tag = Tag.query.filter_by(id=tag_id, user_id=current_user.id).first()
            if tag:
                selected_tags.append(tag)
            tag_index += 1
        
        # 向后兼容单标签上传
        if not selected_tags:
            single_tag_id = request.form.get('tag_id')
            if single_tag_id:
                tag = Tag.query.filter_by(id=single_tag_id, user_id=current_user.id).first()
                if tag:
                    selected_tags.append(tag)
        
        # 如果提供了 ASR 高级选项
        language = request.form.get('language', '')
        min_speakers = request.form.get('min_speakers') or None
        max_speakers = request.form.get('max_speakers') or None
        
        # 如果提供了则转换为 int
        if min_speakers:
            try:
                min_speakers = int(min_speakers)
            except (ValueError, TypeError):
                min_speakers = None
        if max_speakers:
            try:
                max_speakers = int(max_speakers)
            except (ValueError, TypeError):
                max_speakers = None
        
        # 应用优先级层次:用户输入 > 标签默认值 > 环境变量 > 自动检测
        
        # 如果选择了标签且用户未明确提供值,应用标签默认值
        # 使用第一个标签的默认值(最高优先级)
        if selected_tags:
            first_tag = selected_tags[0]
            if not language and first_tag.default_language:
                language = first_tag.default_language
            if min_speakers is None and first_tag.default_min_speakers:
                min_speakers = first_tag.default_min_speakers
            if max_speakers is None and first_tag.default_max_speakers:
                max_speakers = first_tag.default_max_speakers
        
        # 如果仍未设置值,应用环境变量默认值
        if min_speakers is None and ASR_MIN_SPEAKERS:
            try:
                min_speakers = int(ASR_MIN_SPEAKERS)
            except (ValueError, TypeError):
                min_speakers = None
        if max_speakers is None and ASR_MAX_SPEAKERS:
            try:
                max_speakers = int(ASR_MAX_SPEAKERS)
            except (ValueError, TypeError):
                max_speakers = None

        recording = create_recording_from_existing_file(
            filepath=filepath,
            original_filename=original_filename,
            notes=notes,
            selected_tags=selected_tags,
            language=language,
            min_speakers=min_speakers,
            max_speakers=max_speakers,
            mime_type=mime_type,
            processing_source='upload'
        )

        return jsonify(recording.to_dict()), 202

    except RequestEntityTooLarge:
        max_size_mb = flask_app.config['MAX_CONTENT_LENGTH'] / (1024 * 1024)
        app_logger.warning(f"上传失败:文件太大(>{max_size_mb}MB)")
        return jsonify({
            'error': f'文件太大。最大大小为 {max_size_mb:.0f} MB。',
            'max_size_mb': max_size_mb
        }), 413
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"文件上传时出错: {e}", exc_info=True)
        return jsonify({'error': '上传过程中发生意外错误'}), 500


# ==================== 录音状态和音频相关路由 ====================

@recording_bp.route('/status/<int:recording_id>', methods=['GET'])
@login_required
def get_status(recording_id):
    """检查转录/摘要状态"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限查看此录音'}), 403

        # 确保事件已加载(刷新录音以获取最新关系)
        db.session.refresh(recording)

        return jsonify(recording.to_dict())
    except Exception as e:
        app_logger.error(f"获取录音 {recording_id} 状态时出错: {e}", exc_info=True)
        return jsonify({'error': '发生意外错误'}), 500


@recording_bp.route('/audio/<int:recording_id>')
@login_required
def get_audio(recording_id):
    """获取音频文件"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording or not recording.audio_path:
            return jsonify({'error': '录音或音频文件未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限访问此音频文件'}), 403
        if not os.path.exists(recording.audio_path):
            app_logger.error(f"音频文件在服务器上丢失: {recording.audio_path}")
            return jsonify({'error': '音频文件在服务器上丢失'}), 404
        return send_file(recording.audio_path)
    except Exception as e:
        app_logger.error(f"为录音 {recording_id} 提供音频时出错: {e}", exc_info=True)
        return jsonify({'error': '发生意外错误'}), 500


# ==================== 录音更新相关路由 ====================

@recording_bp.route('/recording/<int:recording_id>/update_transcription', methods=['POST'])
@login_required
def update_transcription(recording_id):
    """更新录音的转录内容"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限编辑此录音'}), 403

        data = request.json
        new_transcription = data.get('transcription')

        if new_transcription is None:
            return jsonify({'error': '未提供转录数据'}), 400

        # 传入的数据可能是 JSON 字符串(来自 ASR 编辑)或纯文本
        recording.transcription = new_transcription
        
        # 可选:如果转录改变,我们可能想要指示摘要已过时
        # 目前,我们只保存转录。"重新生成摘要"按钮可能是一个好的后续功能

        db.session.commit()
        app_logger.info(f"录音 {recording_id} 的转录已更新")
        
        return jsonify({'success': True, 'message': '转录更新成功', 'recording': recording.to_dict()})

    except Exception as e:
        db.session.rollback()
        app_logger.error(f"更新录音 {recording_id} 转录时出错: {e}", exc_info=True)
        return jsonify({'error': '更新转录时发生意外错误'}), 500


@recording_bp.route('/recording/<int:recording_id>/toggle_inbox', methods=['POST'])
@login_required
def toggle_inbox(recording_id):
    """切换收件箱状态"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限修改此录音'}), 403

        # 切换收件箱状态
        recording.is_inbox = not recording.is_inbox
        db.session.commit()
        
        return jsonify({'success': True, 'is_inbox': recording.is_inbox})
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"切换录音 {recording_id} 收件箱状态时出错: {e}", exc_info=True)
        return jsonify({'error': '发生意外错误'}), 500


@recording_bp.route('/recording/<int:recording_id>/toggle_highlight', methods=['POST'])
@login_required
def toggle_highlight(recording_id):
    """切换高亮状态"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限修改此录音'}), 403

        # 切换高亮状态
        recording.is_highlighted = not recording.is_highlighted
        db.session.commit()
        
        return jsonify({'success': True, 'is_highlighted': recording.is_highlighted})
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"切换录音 {recording_id} 高亮状态时出错: {e}", exc_info=True)
        return jsonify({'error': '发生意外错误'}), 500


# ==================== 录音重新处理相关路由 ====================

@recording_bp.route('/recording/<int:recording_id>/reprocess_transcription', methods=['POST'])
@login_required
def reprocess_transcription(recording_id):
    """重新处理录音的转录"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限重新处理此录音'}), 403

        if not recording.audio_path or not os.path.exists(recording.audio_path):
            return jsonify({'error': '未找到用于重新处理的音频文件'}), 404

        if recording.status in ['PROCESSING', 'SUMMARIZING']:
            return jsonify({'error': '录音正在处理中'}), 400

        # --- 在重新处理前转换文件(如果需要) ---
        filepath = recording.audio_path
        filename_for_asr = recording.original_filename or os.path.basename(filepath)
        filename_lower = filename_for_asr.lower()

        supported_formats = ('.wav', '.mp3', '.flac')
        if not filename_lower.endswith(supported_formats):
            app_logger.info(f"重新处理:将 {filename_lower} 格式转换为高质量 MP3")
            base_filepath, file_ext = os.path.splitext(filepath)
            temp_mp3_filepath = f"{base_filepath}_temp.mp3"
            final_mp3_filepath = f"{base_filepath}.mp3"

            try:
                # 转换为高质量 MP3(128kbps, 44.1kHz)
                subprocess.run(
                    ['ffmpeg', '-i', filepath, '-y', '-acodec', 'libmp3lame', '-b:a', '128k', '-ar', '44100', temp_mp3_filepath],
                    check=True, capture_output=True, text=True
                )
                app_logger.info(f"成功将 {filepath} 转换为 {temp_mp3_filepath} (128kbps MP3)")

                # 如果原始文件与最终 mp3 文件不同,删除它
                if filepath.lower() != final_mp3_filepath.lower():
                    os.remove(filepath)
                
                # 覆盖已有最终文件，避免 Windows 下 os.rename() 因目标存在而失败
                if os.path.exists(final_mp3_filepath):
                    app_logger.info(
                        f"重新处理录音 {recording_id} 时发现旧文件已存在，将覆盖: {final_mp3_filepath}"
                    )
                os.replace(temp_mp3_filepath, final_mp3_filepath)
                
                filepath = final_mp3_filepath
                filename_for_asr = os.path.basename(filepath)
                
                # 使用新路径和 MIME 类型更新数据库
                recording.audio_path = filepath
                recording.mime_type, _ = mimetypes.guess_type(filepath)
                db.session.commit()

            except FileNotFoundError:
                app_logger.error("未找到 ffmpeg 命令。请确保 ffmpeg 已安装并在系统 PATH 中")
                return jsonify({'error': '服务器上未找到音频转换工具(ffmpeg)'}), 500
            except subprocess.CalledProcessError as e:
                app_logger.error(f"ffmpeg 转换失败 {filepath}: {e.stderr}")
                return jsonify({'error': f'音频文件转换失败: {e.stderr}'}), 500

        # --- 继续重新处理 ---
        recording.transcription = None
        recording.summary = None
        recording.status = 'PROCESSING'

        # 清除现有事件,因为它们依赖于转录
        Event.query.filter_by(recording_id=recording_id).delete()

        db.session.commit()

        # 刷新录音对象以确保它具有最新的提交数据
        db.session.refresh(recording)

        app_logger.info(f"开始重新处理录音 {recording_id} 的转录")

        # 决定使用哪种转录方法
        if USE_ASR_ENDPOINT:
            app_logger.info(f"使用 ASR 端点重新处理录音 {recording_id}")
            
            data = request.json or {}
            language = data.get('language') or (recording.owner.transcription_language if recording.owner else None)
            min_speakers = data.get('min_speakers') or None
            max_speakers = data.get('max_speakers') or None
            
            # 如果提供了则转换为 int
            if min_speakers:
                try:
                    min_speakers = int(min_speakers)
                except (ValueError, TypeError):
                    min_speakers = None
            if max_speakers:
                try:
                    max_speakers = int(max_speakers)
                except (ValueError, TypeError):
                    max_speakers = None
            
            # 如果没有用户提供输入,应用标签默认值(从此录音的所有标签合并默认值)
            if (min_speakers is None or max_speakers is None) and recording.tags:
                # 获取标签默认值(使用按顺序排列的标签的第一个非 None 值)
                for tag_association in sorted(recording.tag_associations, key=lambda x: x.order):
                    tag = tag_association.tag
                    if min_speakers is None and tag.default_min_speakers:
                        min_speakers = tag.default_min_speakers
                    if max_speakers is None and tag.default_max_speakers:
                        max_speakers = tag.default_max_speakers
                    # 一旦我们有两个值就停止
                    if min_speakers is not None and max_speakers is not None:
                        break
            
            # 如果仍未有值,应用环境变量默认值(重新处理层次:用户输入 > 标签默认值 > 环境变量 > 自动检测)
            if min_speakers is None and ASR_MIN_SPEAKERS:
                try:
                    min_speakers = int(ASR_MIN_SPEAKERS)
                except (ValueError, TypeError):
                    min_speakers = None
            if max_speakers is None and ASR_MAX_SPEAKERS:
                try:
                    max_speakers = int(ASR_MAX_SPEAKERS)
                except (ValueError, TypeError):
                    max_speakers = None
            if 'ASR_DIARIZE' in os.environ:
                diarize_setting = ASR_DIARIZE
            elif USE_ASR_ENDPOINT:
                # 使用 ASR 端点时,使用配置的 ASR_DIARIZE 值
                diarize_setting = ASR_DIARIZE
            else:
                diarize_setting = recording.owner.diarize if recording.owner else False

            start_time = datetime.utcnow()
            thread = threading.Thread(
                target=transcribe_audio_asr,
                args=(flask_app.app_context(), recording.id, filepath, filename_for_asr, start_time),
                kwargs={
                    'mime_type': recording.mime_type,
                    'language': language,
                    'diarize': diarize_setting,
                    'min_speakers': min_speakers,
                    'max_speakers': max_speakers,
                    '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': app_logger,
                    'UPLOAD_FOLDER': flask_app.config.get('UPLOAD_FOLDER')
                }
            )
        else:
            app_logger.info(f"使用标准转录 API 重新处理录音 {recording_id}")
            start_time = datetime.utcnow()
            thread = threading.Thread(
                target=transcribe_audio_task,
                args=(flask_app.app_context(), recording.id, filepath, filename_for_asr, start_time),
                kwargs={
                    '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': app_logger,
                    'UPLOAD_FOLDER': flask_app.config.get('UPLOAD_FOLDER')
                }
            )
        
        thread.start()

        return jsonify({
            'success': True,
            'message': '转录重新处理已启动',
            'recording': recording.to_dict()
        })

    except Exception as e:
        db.session.rollback()
        app_logger.error(f"重新处理录音 {recording_id} 转录时出错: {e}")
        return jsonify({'error': str(e)}), 500


@recording_bp.route('/recording/<int:recording_id>/reprocess_summary', methods=['POST'])
@login_required
def reprocess_summary(recording_id):
    """重新处理录音的摘要(需要现有转录)"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限重新处理此录音'}), 403
            
        # 检查转录是否存在
        if not recording.transcription or len(recording.transcription.strip()) < 10:
            return jsonify({'error': '没有可用的有效转录用于生成摘要'}), 400
            
        # 检查是否正在处理
        if recording.status in ['PROCESSING', 'SUMMARIZING']:
            return jsonify({'error': '录音正在处理中'}), 400
            
        # 检查 OpenRouter 客户端是否可用
        if client is None:
            return jsonify({'error': '摘要服务不可用(OpenRouter 客户端未配置)'}), 503
            
        # 将状态设置为 SUMMARIZING 并清除现有摘要
        recording.summary = None
        recording.status = 'SUMMARIZING'

        # 清除现有事件,因为它们可能在摘要生成期间重新提取
        Event.query.filter_by(recording_id=recording_id).delete()

        db.session.commit()

        # 刷新录音对象以确保它具有最新的提交数据
        db.session.refresh(recording)

        app_logger.info(f"开始重新处理录音 {recording_id} 的摘要")
        
        # 在后台线程中启动摘要生成
        def reprocess_summary_task(app_context, recording_id):
            app_logger.info(f"使用 generate_summary_only_task 开始重新处理录音 {recording_id} 的摘要")
            generate_summary_only_task(app_context, recording_id)
        
        thread = threading.Thread(
            target=reprocess_summary_task,
            args=(flask_app.app_context(), recording.id)
        )
        thread.start()
        
        return jsonify({
            'success': True, 
            'message': '摘要重新处理已启动',
            'recording': recording.to_dict()
        })
        
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"重新处理录音 {recording_id} 摘要时出错: {e}")
        return jsonify({'error': str(e)}), 500


@recording_bp.route('/recording/<int:recording_id>/reset_status', methods=['POST'])
@login_required
def reset_status(recording_id):
    """重置卡住或失败的录音状态"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限修改此录音'}), 403

        # 允许重置如果它卡住或失败
        if recording.status in ['PROCESSING', 'SUMMARIZING', 'FAILED']:
            recording.status = 'FAILED'
            recording.error_message = "从卡住或失败状态手动重置"
            db.session.commit()
            app_logger.info(f"手动重置录音 {recording_id} 状态为 FAILED")
            return jsonify({'success': True, 'message': '录音状态已重置', 'recording': recording.to_dict()})
        else:
            return jsonify({'error': f'录音处于无法重置的状态。当前状态: {recording.status}'}), 400

    except Exception as e:
        db.session.rollback()
        app_logger.error(f"重置录音 {recording_id} 状态时出错: {e}")
        return jsonify({'error': str(e)}), 500


# ==================== 说话人识别相关路由 ====================

@recording_bp.route('/recording/<int:recording_id>/update_speakers', methods=['POST'])
@login_required
def update_speakers(recording_id):
    """使用提供的名称更新转录中的说话人标签"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限编辑此录音'}), 403

        data = request.json
        speaker_map = data.get('speaker_map')
        regenerate_summary = data.get('regenerate_summary', False)

        if not speaker_map:
            return jsonify({'error': '未提供说话人映射'}), 400

        transcription_text = recording.transcription
        is_json = False
        try:
            transcription_data = json.loads(transcription_text)
            # 检查新的简化 JSON 格式(段对象列表)
            is_json = isinstance(transcription_data, list)
        except (json.JSONDecodeError, TypeError):
            is_json = False

        speaker_names_used = []

        if is_json:
            # 处理新的简化 JSON 转录(段列表)
            for segment in transcription_data:
                original_speaker_label = segment.get('speaker')
                if original_speaker_label in speaker_map:
                    new_name_info = speaker_map[original_speaker_label]
                    new_name = new_name_info.get('name', '').strip()
                    if new_name_info.get('isMe'):
                        new_name = current_user.name or 'Me'
                    
                    if new_name:
                        segment['speaker'] = new_name
                        if new_name not in speaker_names_used:
                            speaker_names_used.append(new_name)
            
            recording.transcription = json.dumps(transcription_data)
            
            # 仅从实际给出名称的说话人更新参与者(不是默认标签)
            final_speakers = set()
            for seg in transcription_data:
                speaker = seg.get('speaker')
                if speaker and str(speaker).strip():
                    # 仅包括已给出实际名称的说话人(不是默认标签如 "SPEAKER_01", "SPEAKER_09" 等)
                    # 检查此说话人是否已用真实名称更新(不是默认标签)
                    if not re.match(r'^SPEAKER_\d+$', str(speaker), re.IGNORECASE):
                        final_speakers.add(speaker)
            recording.participants = ', '.join(sorted(list(final_speakers)))

        else:
            # 处理纯文本转录
            new_participants = []
            for speaker_label, new_name_info in speaker_map.items():
                new_name = new_name_info.get('name', '').strip()
                if new_name_info.get('isMe'):
                    new_name = current_user.name or 'Me'
                
                if new_name:
                    transcription_text = re.sub(r'\[\s*' + re.escape(speaker_label) + r'\s*\]', f'[{new_name}]', transcription_text, flags=re.IGNORECASE)
                    if new_name not in new_participants:
                        new_participants.append(new_name)
            
            recording.transcription = transcription_text
            if new_participants:
                recording.participants = ', '.join(new_participants)
            speaker_names_used = new_participants

        # 更新说话人使用统计
        if speaker_names_used:
            update_speaker_usage(speaker_names_used)
        
        db.session.commit()

        if regenerate_summary:
            app_logger.info(f"在说话人更新后为录音 {recording_id} 重新生成摘要")
            thread = threading.Thread(
                target=generate_summary_only_task,
                args=(flask_app.app_context(), recording.id)
            )
            thread.start()
        
        return jsonify({
            'success': True, 
            'message': '说话人更新成功',
            'recording': recording.to_dict()
        })

    except Exception as e:
        db.session.rollback()
        app_logger.error(f"更新录音 {recording_id} 说话人时出错: {e}", exc_info=True)
        return jsonify({'error': str(e)}), 500


@recording_bp.route('/recording/<int:recording_id>/auto_identify_speakers', methods=['POST'])
@login_required
def auto_identify_speakers(recording_id):
    """
    使用 LLM 自动识别转录中的说话人
    仅识别尚未被识别的说话人
    """
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404

        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限修改此录音'}), 403

        if not recording.transcription:
            return jsonify({'error': '没有可用的转录用于说话人识别'}), 400

        # 从请求获取当前说话人映射
        data = request.json or {}
        current_speaker_map = data.get('current_speaker_map', {})
        
        # 从转录中提取所有说话人标签
        formatted_transcription = format_transcription_for_llm(recording.transcription)
        all_labels = re.findall(r'\[(SPEAKER_\d+)\]', formatted_transcription)
        seen = set()
        speaker_labels = [x for x in all_labels if not (x in seen or seen.add(x))]
        
        # 过滤掉已分配名称的说话人
        unidentified_speakers = []
        for speaker_label in speaker_labels:
            speaker_info = current_speaker_map.get(speaker_label, {})
            speaker_name = speaker_info.get('name', '').strip() if isinstance(speaker_info, dict) else str(speaker_info).strip()
            
            # 仅包括没有名称或名称为空的说话人
            if not speaker_name:
                unidentified_speakers.append(speaker_label)
        
        if not unidentified_speakers:
            return jsonify({'success': True, 'speaker_map': {}, 'message': '所有说话人已被识别'})

        # 仅使用未识别的说话人调用辅助函数
        speaker_map = identify_unidentified_speakers_from_text(recording.transcription, unidentified_speakers)

        return jsonify({'success': True, 'speaker_map': speaker_map})

    except ValueError as ve:
        # 处理 API 密钥未设置的情况
        return jsonify({'error': str(ve)}), 503
    except Exception as e:
        app_logger.error(f"录音 {recording_id} 自动说话人识别时出错: {e}", exc_info=True)
        return jsonify({'error': f'发生意外错误: {e}'}), 500


# ==================== 摘要生成相关路由 ====================

@recording_bp.route('/recording/<int:recording_id>/generate_summary', methods=['POST'])
@login_required
def generate_summary_endpoint(recording_id):
    """为没有摘要的录音生成摘要"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限为此录音生成摘要'}), 403
            
        # 检查转录是否存在
        if not recording.transcription or len(recording.transcription.strip()) < 10:
            return jsonify({'error': '没有可用的有效转录用于生成摘要'}), 400
            
        # 检查是否正在处理
        if recording.status in ['PROCESSING', 'SUMMARIZING']:
            return jsonify({'error': '录音正在处理中'}), 400
            
        # 检查 OpenRouter 客户端是否可用
        if client is None:
            return jsonify({'error': '摘要服务不可用(OpenRouter 客户端未配置)'}), 503
            
        app_logger.info(f"开始为录音 {recording_id} 生成摘要")
        
        # 在后台线程中生成摘要
        thread = threading.Thread(
            target=generate_summary_only_task,
            args=(flask_app.app_context(), recording_id)
        )
        thread.start()
        
        return jsonify({
            'success': True, 
            'message': '摘要生成已启动'
        })
        
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"为录音 {recording_id} 启动摘要生成时出错: {e}")
        return jsonify({'error': str(e)}), 500


# ==================== 录音删除相关路由 ====================

@recording_bp.route('/recording/<int:recording_id>', methods=['DELETE'])
@login_required
def delete_recording(recording_id):
    """删除录音"""
    try:
        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
            
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限删除此录音'}), 403

        # 首先删除音频文件
        try:
            if recording.audio_path and os.path.exists(recording.audio_path):
                os.remove(recording.audio_path)
                app_logger.info(f"已删除音频文件: {recording.audio_path}")
        except Exception as e:
            app_logger.error(f"删除音频文件 {recording.audio_path} 时出错: {e}")

        # 如果启用了 Inquire 模式,记录嵌入清理日志
        if ENABLE_INQUIRE_MODE:
            chunk_count = TranscriptChunk.query.filter_by(recording_id=recording_id).count()
            if chunk_count > 0:
                app_logger.info(f"正在删除录音 {recording_id} 的 {chunk_count} 个转录块和嵌入")

        # 删除数据库记录(级联将处理块/嵌入)
        db.session.delete(recording)
        db.session.commit()
        app_logger.info(f"已删除录音记录 ID: {recording_id}")
        
        if ENABLE_INQUIRE_MODE and chunk_count > 0:
            app_logger.info(f"成功删除录音 {recording_id} 的嵌入和块")

        return jsonify({'success': True})
    except Exception as e:
        db.session.rollback()
        app_logger.error(f"删除录音 {recording_id} 时出错: {e}", exc_info=True)
        return jsonify({'error': '删除时发生意外错误'}), 500


# ==================== 录音元数据保存相关路由 ====================

@recording_bp.route('/save', methods=['POST'])
@login_required
def save_metadata():
    """保存录音元数据"""
    try:
        data = request.json
        if not data:
            return jsonify({'error': '未提供数据'}), 400
        recording_id = data.get('id')
        if not recording_id:
            return jsonify({'error': '未提供录音ID'}), 400

        recording = db.session.get(Recording, recording_id)
        if not recording:
            return jsonify({'error': '录音未找到'}), 404
        
        # 检查录音是否属于当前用户
        if recording.user_id and recording.user_id != current_user.id:
            return jsonify({'error': '您没有权限编辑此录音'}), 403

        # 更新字段（如果提供）
        if 'title' in data:
            recording.title = data['title']
        if 'participants' in data:
            recording.participants = data['participants']
        if 'notes' in data:
            recording.notes = data['notes']
        if 'summary' in data:
            recording.summary = data['summary']
        if 'is_inbox' in data:
            recording.is_inbox = data['is_inbox']
        if 'is_highlighted' in data:
            recording.is_highlighted = data['is_highlighted']
        if 'meeting_date' in data:
            try:
                # 尝试解析日期字符串（例如："YYYY-MM-DD"）
                date_str = data['meeting_date']
                if date_str:
                    recording.meeting_date = datetime.strptime(date_str, '%Y-%m-%d').date()
                else:
                    recording.meeting_date = None  # 允许清除日期
            except (ValueError, TypeError) as e:
                app_logger.warning(f"无法解析会议日期 '{data.get('meeting_date')}': {e}")

        # 不在此处更新转录或状态
        db.session.commit()
        return jsonify({'success': True, 'recording': recording.to_dict()})

    except Exception as e:
        db.session.rollback()
        app_logger.error(f"保存录音 {recording_id} 元数据时出错: {e}", exc_info=True)
        return jsonify({'error': '保存时发生意外错误'}), 500


# ==================== ASR 文本矫正相关路由 ====================

@recording_bp.route('/api/asr/correct', methods=['POST'])
@login_required
def correct_asr_text():
    """使用 LLM 矫正 ASR 识别结果"""
    try:
        data = request.json
        if not data:
            return jsonify({'error': '未提供数据'}), 400
        
        asr_text = data.get('text')
        if not asr_text:
            return jsonify({'error': '未提供文本'}), 400

        scene = _normalize_asr_scene(data.get('scene') or request.args.get('scene'))
        
        # 使用 LLM 矫正 ASR 文本
        system_prompt = SystemSetting.get_setting(
            'correct_system_prompt',
            '请严格纠正以下ASR文本中的错误（如同音字、标点符号、语法错误等），并保持其原意、格式与结构不变。'
        )
        user_prompt = f"请转换以下口语转录文本：\n\n{asr_text}"
        
        completion = call_llm_completion(
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.3,  # 较低温度保证准确性
            max_tokens=2000,
            stream=False
        )
        
        corrected_text = completion.choices[0].message.content.strip()
        final_corrected_text = extract_corrected_text(corrected_text, asr_text)
        standard_word_mapping_config = SystemConfig.query.filter_by(
            config_key=_standard_word_mappings_key_for_scene(scene)
        ).first()
        mapped_corrected_text, applied_standard_word_mappings = apply_standard_word_mappings_with_details(
            final_corrected_text,
            standard_word_mapping_config.config_value if standard_word_mapping_config else None
        )
        log_message = (
            f"结果: {mapped_corrected_text}, ASR: {asr_text}, 矫正: {corrected_text[:50]}"
        )
        if applied_standard_word_mappings:
            mapping_summary = ", ".join(
                f"{mapping['from']}->{mapping['to']}"
                for mapping in applied_standard_word_mappings
            )
            log_message = f"{log_message}, 映射: [{mapping_summary}]"
        app_logger.info(log_message)

        return jsonify({
            'corrected_text': mapped_corrected_text,
            'original_text': asr_text,
            'llm_corrected_text': final_corrected_text
        })
        
    except Exception as e:
        app_logger.error(f"矫正 ASR 文本时出错: {str(e)}")
        return jsonify({'error': str(e)}), 500


# ==================== 外部摘要查询相关路由 ====================

@recording_bp.route('/api/external/summaries', methods=['GET'])
def get_completed_summaries_by_date():
    """
    根据 user_id 和创建日期获取已完成的录音摘要列表
    返回格式包含 summary 和 created_at 的字典
    """
    try:
        # 1. 获取入参
        user_id = request.args.get('user_id')
        created_at_str = request.args.get('created_at')

        # 参数校验
        if not user_id or not created_at_str:
            return jsonify({'error': '缺少必需参数: user_id 或 created_at'}), 400

        try:
            # 解析日期 YYYY-MM-DD
            target_date = datetime.strptime(created_at_str, '%Y-%m-%d').date()
        except ValueError:
            return jsonify({'error': '日期格式无效。请使用 YYYY-MM-DD'}), 400

        # 2. 筛选数据
        stmt = select(Recording.summary, Recording.created_at).where(
            Recording.user_id == user_id,
            Recording.status == 'COMPLETED',
            func.date(Recording.created_at) == target_date
        ).order_by(Recording.created_at.desc())

        # 执行查询，获取结果列表
        results = db.session.execute(stmt).all()

        # 3. 组装返回结果
        summary_list = []
        for row in results:
            summary_text = row[0]
            created_at_val = row[1]
            
            if summary_text:  # 确保摘要不为空
                summary_list.append({
                    "summary": summary_text,
                    "created_at": created_at_val.strftime('%Y-%m-%d %H:%M:%S') if created_at_val else ""
                })

        return jsonify({
            'success': True,
            'count': len(summary_list),
            'summaries': summary_list
        })

    except Exception as e:
        app_logger.error(f"获取外部摘要时出错: {e}")
        return jsonify({'error': str(e)}), 500

