# 声纹蓝图
# 处理声纹的增删改查、音频获取及平台同步

import os
import tempfile
import mimetypes
import subprocess
from pathlib import Path
from datetime import datetime

from flask import Blueprint, request, jsonify, send_file, current_app
from flask_login import login_required, current_user
from werkzeug.utils import secure_filename

from src.clients import db, voiceprint_client, ASR_BASE_URL
from src.models import Voiceprint, Recording
from src.services import sync_voiceprint_to_platform

# 创建声纹蓝图
voiceprint_bp = Blueprint('voiceprint', __name__)

# 全局对象（从 app.py 导入）
limiter = None
csrf = None
bcrypt = None

def init_voiceprint_bp(limiter_instance, csrf_instance, bcrypt_instance):
    """初始化蓝图所需的全局对象"""
    global limiter, csrf, bcrypt
    limiter = limiter_instance
    csrf = csrf_instance
    bcrypt = bcrypt_instance


def convert_to_wav(input_path):
    """
    将音频文件转换为WAV格式
    Args:
        input_path: 输入音频文件路径
    Returns:
        str: 转换后的WAV文件路径
    Raises:
        Exception: 转换失败时抛出异常
    """
    # 检查文件是否存在
    if not os.path.exists(input_path):
        raise FileNotFoundError(f"音频文件不存在: {input_path}")

    # 获取文件MIME类型
    mime_type, _ = mimetypes.guess_type(input_path)
    current_app.logger.info(f"mime_type: {mime_type}, input_path:{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进行格式转换
        # 参数说明：
        # -y: 覆盖输出文件
        # -i: 输入文件
        # -acodec pcm_s16le: 使用PCM 16位编码
        # -ar 16000: 采样率16kHz（可根据需要调整）
        # -ac 1: 单声道
        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)}")


@voiceprint_bp.route('/api/voiceprints', methods=['GET'])
@login_required
def get_voiceprints():
    """获取声纹列表"""
    try:
        voiceprints = Voiceprint.query.all()
        return jsonify({
            'success': True,
            'voiceprints': [vp.to_dict() for vp in voiceprints]
        })
    except Exception as e:
        current_app.logger.error(f"获取声纹列表失败: {e}")
        return jsonify({'error': str(e)}), 500


@voiceprint_bp.route('/api/voiceprints', methods=['POST'])
@login_required
def create_voiceprint():
    """创建声纹"""
    converted_file_path = None
    original_file_path = None

    try:
        # 检查是否有文件上传
        if 'voice_data' not in request.files:
            return jsonify({'error': '没有上传音频文件'}), 400

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

        # 获取表单数据
        name = request.form.get('name')
        if not name:
            return jsonify({'error': '声纹名称不能为空'}), 400

        # 检查名称是否已存在
        existing_voiceprint = Voiceprint.query.filter_by(name=name).first()
        if existing_voiceprint:
            return jsonify({'error': '声纹名称已存在'}), 400

        # 创建上传目录
        voiceprint_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], 'voiceprint')
        os.makedirs(voiceprint_dir, exist_ok=True)

        # 保存原始音频文件到临时位置
        temp_filename = secure_filename(f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}")
        temp_filepath = os.path.join(voiceprint_dir, temp_filename)
        file.save(temp_filepath)
        original_file_path = temp_filepath

        # 转换为WAV格式
        converted_file_path = convert_to_wav(temp_filepath)

        # 保存转换后的文件
        final_filename = secure_filename(f"{name}_{datetime.now().strftime('%Y%m%d%H%M%S')}.wav")
        final_filepath = os.path.join(voiceprint_dir, final_filename)

        # 如果转换后的文件不是原始文件，则移动
        if converted_file_path != temp_filepath:
            import shutil
            shutil.move(converted_file_path, final_filepath)
            converted_file_path = final_filepath
        else:
            # 如果是原始WAV文件，重命名
            os.rename(temp_filepath, final_filepath)
            converted_file_path = final_filepath

        # 创建声纹记录
        voiceprint = Voiceprint(
            name=name,
            audio_path=final_filepath
        )
        db.session.add(voiceprint)
        db.session.commit()

        # 同步到平台
        try:
            sync_voiceprint_to_platform(voiceprint, 'add')
        except Exception as sync_error:
            # 如果同步失败，删除本地记录
            db.session.delete(voiceprint)
            db.session.commit()
            # 删除已保存的文件
            if os.path.exists(final_filepath):
                os.remove(final_filepath)
            return jsonify({'error': f'创建声纹成功但同步到平台失败: {str(sync_error)}'}), 500

        return jsonify({
            'success': True,
            'message': '声纹创建成功',
            'voiceprint': voiceprint.to_dict()
        }), 201

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"创建声纹失败: {e}")

        # 清理临时文件
        if original_file_path and os.path.exists(original_file_path):
            try:
                os.remove(original_file_path)
            except:
                pass
        if converted_file_path and converted_file_path != original_file_path and os.path.exists(converted_file_path):
            try:
                os.remove(converted_file_path)
            except:
                pass

        return jsonify({'error': str(e)}), 500


@voiceprint_bp.route('/api/voiceprints/<int:voiceprint_id>', methods=['PUT'])
@login_required
def update_voiceprint(voiceprint_id):
    """更新声纹"""
    converted_file_path = None
    original_file_path = None

    try:
        voiceprint = Voiceprint.query.get_or_404(voiceprint_id)

        # 获取表单数据
        name = request.form.get('name')
        if not name or not name.strip():
            return jsonify({'error': '声纹名称不能为空'}), 400

        # 检查名称是否被其他声纹使用
        existing_voiceprint = Voiceprint.query.filter(
            Voiceprint.name == name,
            Voiceprint.id != voiceprint_id
        ).first()
        if existing_voiceprint:
            return jsonify({'error': '声纹名称已存在'}), 400

        # 保存旧名称用于平台同步
        old_name = voiceprint.name

        # 处理文件上传（如果有）
        file = request.files.get('voice_data')
        if file and file.filename != '':
            # 创建上传目录
            voiceprint_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], 'voiceprint')
            os.makedirs(voiceprint_dir, exist_ok=True)

            # 保存原始音频文件到临时位置
            temp_filename = secure_filename(f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}")
            temp_filepath = os.path.join(voiceprint_dir, temp_filename)
            file.save(temp_filepath)
            original_file_path = temp_filepath

            # 转换为WAV格式
            converted_file_path = convert_to_wav(temp_filepath)

            # 保存转换后的文件
            final_filename = secure_filename(f"{name}_{datetime.now().strftime('%Y%m%d%H%M%S')}.wav")
            final_filepath = os.path.join(voiceprint_dir, final_filename)

            # 如果转换后的文件不是原始文件，则移动
            if converted_file_path != temp_filepath:
                import shutil
                shutil.move(converted_file_path, final_filepath)
                converted_file_path = final_filepath
            else:
                # 如果是原始WAV文件，重命名
                os.rename(temp_filepath, final_filepath)
                converted_file_path = final_filepath

            # 删除旧文件
            if os.path.exists(voiceprint.audio_path):
                os.remove(voiceprint.audio_path)

            voiceprint.audio_path = final_filepath

        # 更新名称
        voiceprint.name = name
        db.session.commit()

        # 同步到平台：先删除旧记录，再添加新记录
        try:
            # 创建临时对象用于删除
            temp_voiceprint = Voiceprint(name=old_name, audio_path='')
            sync_voiceprint_to_platform(temp_voiceprint, 'delete')

            # 添加更新后的记录
            sync_voiceprint_to_platform(voiceprint, 'add')

        except Exception as sync_error:
            current_app.logger.error(f"同步声纹更新到平台失败: {str(sync_error)}")
            return jsonify({'error': f'更新声纹成功但同步到平台失败: {str(sync_error)}'}), 500

        return jsonify({
            'success': True,
            'message': '声纹更新成功',
            'voiceprint': voiceprint.to_dict()
        })

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"更新声纹失败: {e}")

        # 清理临时文件
        if original_file_path and os.path.exists(original_file_path):
            try:
                os.remove(original_file_path)
            except:
                pass
        if converted_file_path and converted_file_path != original_file_path and os.path.exists(converted_file_path):
            try:
                os.remove(converted_file_path)
            except:
                pass

        return jsonify({'error': str(e)}), 500


@voiceprint_bp.route('/api/voiceprints/<int:voiceprint_id>', methods=['DELETE'])
@login_required
def delete_voiceprint(voiceprint_id):
    """删除声纹"""
    try:
        voiceprint = Voiceprint.query.get_or_404(voiceprint_id)

        # 同步到平台
        try:
            sync_voiceprint_to_platform(voiceprint, 'delete')
        except Exception as sync_error:
            current_app.logger.error(f"同步声纹删除到平台失败: {str(sync_error)}")
            return jsonify({'error': f'删除声纹失败: 无法从平台删除 - {str(sync_error)}'}), 500

        # 删除本地文件
        if os.path.exists(voiceprint.audio_path):
            os.remove(voiceprint.audio_path)

        # 删除数据库记录
        db.session.delete(voiceprint)
        db.session.commit()

        return jsonify({
            'success': True,
            'message': '声纹删除成功'
        })

    except Exception as e:
        db.session.rollback()
        current_app.logger.error(f"删除声纹失败: {e}")
        return jsonify({'error': str(e)}), 500


@voiceprint_bp.route('/api/voiceprints/sync-local-to-platform', methods=['POST'])
@login_required
def sync_local_to_platform():
    """将本地比平台多的声纹同步到平台"""
    try:
        # 检查用户权限，只有管理员可以执行同步
        if not current_user.is_admin:
            return jsonify({'error': '只有管理员可以执行声纹同步操作'}), 403

        # 获取本地声纹列表
        local_voiceprints = Voiceprint.query.all()
        local_names = {vp.name for vp in local_voiceprints}

        # 获取平台声纹列表
        try:
            platform_data = voiceprint_client.get_voice_names()
            voice_list = platform_data.get('data', [])
            platform_names = {item['name'] for item in voice_list if isinstance(item, dict) and 'name' in item}
            # 根据实际API响应结构调整
            current_app.logger.info(f"平台声纹列表: {platform_names}")
        except Exception as e:
            current_app.logger.error(f"获取平台声纹列表失败: {e}")
            return jsonify({'error': f'无法连接到声纹平台: {str(e)}'}), 500

        # 计算需要同步的声纹（本地有但平台没有的）
        to_sync = local_names - platform_names

        if not to_sync:
            return jsonify({
                'success': True,
                'message': '本地和平台声纹已完全同步，无需额外操作',
                'synced': 0,
                'already_synced': len(local_names & platform_names)
            })

        current_app.logger.info(f"发现 {len(to_sync)} 个需要同步到平台的声纹: {to_sync}")

        # 开始同步
        synced_count = 0
        failed_syncs = []

        for voiceprint_name in to_sync:
            try:
                # 查找本地声纹对象
                voiceprint = Voiceprint.query.filter_by(name=voiceprint_name).first()
                if not voiceprint:
                    current_app.logger.warning(f"本地声纹 '{voiceprint_name}' 在数据库中未找到")
                    failed_syncs.append({
                        'name': voiceprint_name,
                        'reason': '本地数据库中未找到该声纹记录'
                    })
                    continue

                # 检查音频文件是否存在
                if not os.path.exists(voiceprint.audio_path):
                    current_app.logger.warning(f"声纹 '{voiceprint_name}' 的音频文件不存在: {voiceprint.audio_path}")
                    failed_syncs.append({
                        'name': voiceprint_name,
                        'reason': f'音频文件不存在: {voiceprint.audio_path}'
                    })
                    continue

                # 同步到平台
                with open(voiceprint.audio_path, 'rb') as audio_file:
                    voiceprint_client.add_voiceprint(voiceprint.name, audio_file)

                synced_count += 1
                current_app.logger.info(f"成功同步声纹到平台: {voiceprint_name}")

            except Exception as e:
                current_app.logger.error(f"同步声纹 '{voiceprint_name}' 失败: {e}")
                failed_syncs.append({
                    'name': voiceprint_name,
                    'reason': str(e)
                })

        # 统计同步结果
        already_synced = len(local_names & platform_names)
        total_local = len(local_names)

        response_data = {
            'success': True,
            'message': f'同步完成，成功同步 {synced_count} 个声纹到平台',
            'sync_summary': {
                'total_local_voiceprints': total_local,
                'already_synced': already_synced,
                'newly_synced': synced_count,
                'failed_syncs': len(failed_syncs)
            }
        }

        if failed_syncs:
            response_data['failed_syncs'] = failed_syncs
            response_data['message'] += f'，{len(failed_syncs)} 个同步失败'

        # 如果没有任何需要同步的声纹或所有同步都失败
        if synced_count == 0:
            if not to_sync:
                response_data['message'] = '本地和平台声纹已完全同步，无需额外操作'
            else:
                response_data['message'] = f'尝试同步 {len(to_sync)} 个声纹，但所有同步都失败'
                response_data['success'] = False

        return jsonify(response_data)

    except Exception as e:
        current_app.logger.error(f"同步本地声纹到平台失败: {e}")
        return jsonify({'error': str(e)}), 500


@voiceprint_bp.route('/api/voiceprints/<int:voiceprint_id>/audio', methods=['GET'])
@login_required
def get_voiceprint_audio(voiceprint_id):
    """获取声纹音频文件"""
    try:
        voiceprint = Voiceprint.query.get_or_404(voiceprint_id)
        if not os.path.exists(voiceprint.audio_path):
            return jsonify({'error': '音频文件不存在'}), 404
        return send_file(voiceprint.audio_path)

    except Exception as e:
        current_app.logger.error(f"获取声纹音频文件失败: {e}")
        return jsonify({'error': str(e)}), 500
