import os, subprocess
import pandas as pd
from typing import Dict, List, Tuple
from pydub import AudioSegment
from core.utils import *
from core.utils.models import *
from pydub import AudioSegment
from pydub.silence import detect_silence
from pydub.utils import mediainfo
from rich import print as rprint

def normalize_audio_volume(audio_path, output_path, target_db = -15.0, format = "wav"):
    """
    标准化音频音量到指定的分贝值
    
    Args:
        audio_path (str): 输入音频文件路径
        output_path (str): 输出音频文件路径
        target_db (float, optional): 目标音量分贝值，默认为-15.0dB
        format (str, optional): 输出音频格式，默认为"wav"
    
    Returns:
        str: 输出文件路径
    
    功能说明:
        - 加载音频文件并获取当前音量
        - 计算需要调整的增益值
        - 应用增益调整音量到目标分贝
        - 导出标准化后的音频文件
    """
    audio = AudioSegment.from_file(audio_path)
    change_in_dBFS = target_db - audio.dBFS
    normalized_audio = audio.apply_gain(change_in_dBFS)
    normalized_audio.export(output_path, format=format)
    rprint(f"[green]✅ Audio normalized from {audio.dBFS:.1f}dB to {target_db:.1f}dB[/green]")
    return output_path

def convert_video_to_audio(video_file: str):
    """
    将视频文件转换为音频文件
    
    Args:
        video_file (str): 输入视频文件路径
    
    功能说明:
        - 创建音频输出目录
        - 检查原始音频文件是否已存在，避免重复转换
        - 使用FFmpeg将视频转换为高质量音频文件
        - 转换参数：
          * -vn: 禁用视频流
          * -c:a libmp3lame: 使用MP3编码器
          * -b:a 32k: 设置音频比特率为32kbps
          * -ar 16000: 设置采样率为16kHz
          * -ac 1: 设置为单声道
          * -metadata encoding=UTF-8: 设置编码为UTF-8
    """
    os.makedirs(_AUDIO_DIR, exist_ok=True)
    if not os.path.exists(_RAW_AUDIO_FILE):
        rprint(f"[blue]🎬➡️🎵 Converting to high quality audio with FFmpeg ......[/blue]")
        subprocess.run([
            'ffmpeg', '-y', '-i', video_file, '-vn',
            '-c:a', 'libmp3lame', '-b:a', '32k',
            '-ar', '16000',
            '-ac', '1', 
            '-metadata', 'encoding=UTF-8', _RAW_AUDIO_FILE
        ], check=True, stderr=subprocess.PIPE)
        rprint(f"[green]🎬➡️🎵 Converted <{video_file}> to <{_RAW_AUDIO_FILE}> with FFmpeg\n[/green]")

def get_audio_duration(audio_file: str) -> float:
    """
    使用FFmpeg获取音频文件的时长
    
    Args:
        audio_file (str): 音频文件路径
        
    Returns:
        float: 音频时长（秒），如果获取失败则返回0
        
    功能说明:
        - 调用FFmpeg命令获取音频文件信息
        - 从输出中解析Duration信息
        - 将时:分:秒格式转换为总秒数
        - 异常处理：解析失败时返回0并打印错误信息
    """
    cmd = ['ffmpeg', '-i', audio_file]
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    _, stderr = process.communicate()
    output = stderr.decode('utf-8', errors='ignore')
    
    try:
        duration_str = [line for line in output.split('\n') if 'Duration' in line][0]
        duration_parts = duration_str.split('Duration: ')[1].split(',')[0].split(':')
        duration = float(duration_parts[0])*3600 + float(duration_parts[1])*60 + float(duration_parts[2])
    except Exception as e:
        print(f"[red]❌ Error: Failed to get audio duration: {e}[/red]")
        duration = 0
    return duration

def split_audio(audio_file: str, target_len: float = 30*60, win: float = 60) -> List[Tuple[float, float]]:
    """
    智能音频分割函数，基于静默检测将长音频分割为合适长度的片段
    
    Args:
        audio_file (str): 输入音频文件路径
        target_len (float, optional): 目标分割长度（秒），默认30分钟（30*60）
        win (float, optional): 静默检测窗口大小（秒），默认60秒
        
    Returns:
        List[Tuple[float, float]]: 分割片段列表，每个元组包含(开始时间, 结束时间)
        
    功能说明:
        - 如果音频长度小于目标长度+窗口，直接返回整个音频
        - 在目标切分点前后的窗口内检测静默区域
        - 优先在静默区域进行切分，避免切断正在说话的内容
        - 静默检测参数：
          * 最小静默长度：0.5秒
          * 静默阈值：-30dB
          * 安全边界：在静默区域起始点后0.5秒处切分
        - 如果找不到合适的静默区域，在目标位置强制切分
        - 处理剩余音频：如果剩余长度≤目标长度，直接作为最后一个片段
    """
    ## 在 [target_len-win, target_len+win] 区间内用 pydub 检测静默，切分音频
    rprint(f"[blue]🎙️ Starting audio segmentation {audio_file} {target_len} {win}[/blue]")
    audio = AudioSegment.from_file(audio_file)
    duration = float(mediainfo(audio_file)["duration"])
    if duration <= target_len + win:
        return [(0, duration)]
    segments, pos = [], 0.0
    safe_margin = 0.5  # 静默点前后安全边界，单位秒

    while pos < duration:
        if duration - pos <= target_len:
            segments.append((pos, duration)); break

        threshold = pos + target_len
        ws, we = int((threshold - win) * 1000), int((threshold + win) * 1000)
        
        # 获取完整的静默区域
        silence_regions = detect_silence(audio[ws:we], min_silence_len=int(safe_margin*1000), silence_thresh=-30)
        silence_regions = [(s/1000 + (threshold - win), e/1000 + (threshold - win)) for s, e in silence_regions]
        # 筛选长度足够（至少1秒）且位置适合的静默区域
        valid_regions = [
            (start, end) for start, end in silence_regions 
            if (end - start) >= (safe_margin * 2) and threshold <= start + safe_margin <= threshold + win
        ]
        
        if valid_regions:
            start, end = valid_regions[0]
            split_at = start + safe_margin  # 在静默区域起始点后0.5秒处切分
        else:
            rprint(f"[yellow]⚠️ No valid silence regions found for {audio_file} at {threshold}s, using threshold[/yellow]")
            split_at = threshold
            
        segments.append((pos, split_at)); pos = split_at

    rprint(f"[green]🎙️ Audio split completed {len(segments)} segments[/green]")
    return segments

def process_transcription(result: Dict) -> pd.DataFrame:
    """
    处理语音转录结果，将其转换为结构化的DataFrame格式
    
    Args:
        result (Dict): 包含转录结果的字典，包含segments和words信息
        
    Returns:
        pd.DataFrame: 包含以下列的DataFrame：
                     - text: 词汇文本
                     - start: 开始时间（秒）
                     - end: 结束时间（秒）
                     - speaker_id: 说话人ID（可选）
                     
    功能说明:
        - 遍历所有转录片段和其中的词汇
        - 过滤超长词汇（>30字符）
        - 处理法语引号字符（guillemets）
        - 时间戳处理策略：
          * 正常情况：使用词汇自带的start/end时间
          * 缺失时间戳：
            - 非首词：使用前一个词的结束时间
            - 首词：查找下一个有时间戳的词并使用其时间
        - 提取说话人信息（如果可用）
        - 返回标准化的词汇时间轴数据
    """
    all_words = []
    for segment in result['segments']:
        # Get speaker_id, if not exists, set to None
        speaker_id = segment.get('speaker_id', None)
        
        for word in segment['words']:
            # Check word length
            if len(word["word"]) > 30:
                rprint(f"[yellow]⚠️ Warning: Detected word longer than 30 characters, skipping: {word['word']}[/yellow]")
                continue
                
            # ! For French, we need to convert guillemets to empty strings
            word["word"] = word["word"].replace('»', '').replace('«', '')
            
            if 'start' not in word and 'end' not in word:
                if all_words:
                    # Assign the end time of the previous word as the start and end time of the current word
                    word_dict = {
                        'text': word["word"],
                        'start': all_words[-1]['end'],
                        'end': all_words[-1]['end'],
                        'speaker_id': speaker_id
                    }
                    all_words.append(word_dict)
                else:
                    # If it's the first word, look next for a timestamp then assign it to the current word
                    next_word = next((w for w in segment['words'] if 'start' in w and 'end' in w), None)
                    if next_word:
                        word_dict = {
                            'text': word["word"],
                            'start': next_word["start"],
                            'end': next_word["end"],
                            'speaker_id': speaker_id
                        }
                        all_words.append(word_dict)
                    else:
                        raise Exception(f"No next word with timestamp found for the current word : {word}")
            else:
                # Normal case, with start and end times
                word_dict = {
                    'text': f'{word["word"]}',
                    'start': word.get('start', all_words[-1]['end'] if all_words else 0),
                    'end': word['end'],
                    'speaker_id': speaker_id
                }
                
                all_words.append(word_dict)
    
    return pd.DataFrame(all_words)

def save_results(df: pd.DataFrame):
    """
    保存转录结果到Excel文件，并进行数据清理
    
    Args:
        df (pd.DataFrame): 包含转录结果的DataFrame
        
    功能说明:
        - 创建输出目录（output/log）
        - 数据清理步骤：
          * 移除空文本行
          * 移除超长文本行（>30字符）
          * 为文本内容添加引号格式化
        - 保存清理后的数据到Excel文件
        - 统计并报告清理过程中移除的行数
        - 使用全局变量 _2_CLEANED_CHUNKS 作为输出文件路径
    """
    os.makedirs('output/log', exist_ok=True)

    # Remove rows where 'text' is empty
    initial_rows = len(df)
    df = df[df['text'].str.len() > 0]
    removed_rows = initial_rows - len(df)
    if removed_rows > 0:
        rprint(f"[blue]ℹ️ Removed {removed_rows} row(s) with empty text.[/blue]")
    
    # Check for and remove words longer than 20 characters
    long_words = df[df['text'].str.len() > 30]
    if not long_words.empty:
        rprint(f"[yellow]⚠️ Warning: Detected {len(long_words)} word(s) longer than 30 characters. These will be removed.[/yellow]")
        df = df[df['text'].str.len() <= 30]
    
    df['text'] = df['text'].apply(lambda x: f'"{x}"')
    df.to_excel(_2_CLEANED_CHUNKS, index=False)
    rprint(f"[green]📊 Excel file saved to {_2_CLEANED_CHUNKS}[/green]")

def save_language(language: str):
    """
    保存检测到的语言信息到配置文件
    
    Args:
        language (str): 检测到的语言代码（如 'en', 'zh', 'fr' 等）
        
    功能说明:
        - 将Whisper模型检测到的语言信息保存到配置中
        - 使用配置工具函数 update_key 更新 "whisper.detected_language" 键值
        - 此信息可用于后续的语言相关处理和优化
    """
    update_key("whisper.detected_language", language)