"""
LLM 服务模块 - 处理所有与大语言模型相关的业务逻辑

主要功能：
- LLM API 调用封装
- 响应内容清理和格式化
- 流式响应处理
- 转录文本预处理
"""
import json
import re
import logging

from src.clients.llm_client import client, TEXT_MODEL_BASE_URL, TEXT_MODEL_NAME, TEXT_MODEL_API_KEY

logger = logging.getLogger(__name__)


def format_transcription_for_llm(transcription_text):
    """
    将转录文本格式化为 LLM 可理解的纯文本格式
    
    如果输入是简化的 JSON 格式（包含 speaker 和 sentence），则转换为：
    [Speaker]: sentence
    [Speaker]: sentence
    
    否则直接返回原文本
    
    Args:
        transcription_text: 转录文本（可能是 JSON 字符串或纯文本）
    
    Returns:
        str: 格式化后的纯文本
    """
    try:
        transcription_data = json.loads(transcription_text)
        if isinstance(transcription_data, list):
            # 这是我们的简化 JSON 格式
            formatted_lines = []
            for segment in transcription_data:
                speaker = segment.get('speaker', 'Unknown Speaker')
                sentence = segment.get('sentence', '')
                formatted_lines.append(f"[{speaker}]: {sentence}")
            return "\n".join(formatted_lines)
    except (json.JSONDecodeError, TypeError):
        # 不是 JSON，或者不是我们期望的格式，直接返回原文
        pass
    return transcription_text


def clean_llm_response(text):
    """
    清理 LLM 响应内容，移除思考标签和多余空白
    
    处理推理模型返回的包含 <think> 标签的响应，保留主要内容
    
    Args:
        text: LLM 返回的原始文本
    
    Returns:
        str: 清理后的文本
    """
    if not text:
        return ""
    
    # 移除思考标签及其内容
    # 处理 <think> 和 <thinking> 标签的各种闭合格式
    cleaned = re.sub(r'<think(?:ing)?>.*?</think(?:ing)?>', '', text, flags=re.DOTALL | re.IGNORECASE)
    
    # 同时处理未闭合的思考标签（以防模型没有正确闭合）
    cleaned = re.sub(r'<think(?:ing)?>.*$', '', cleaned, flags=re.DOTALL | re.IGNORECASE)
    
    # 移除任何可能与思考相关的剩余 XML 类标签
    # 但保留 markdown 格式标签
    cleaned = re.sub(r'<(?!/?(?:code|pre|blockquote|p|br|hr|ul|ol|li|h[1-6]|em|strong|b|i|a|img)(?:\s|>|/))[^>]+>', '', cleaned)
    
    # 清理多余的空白，同时保留有意的格式化
    # 移除每行的前导和尾随空白
    lines = cleaned.split('\n')
    cleaned_lines = []
    for line in lines:
        # 保留代码块或列表中的行
        if line.strip() or (len(cleaned_lines) > 0 and cleaned_lines[-1].strip().startswith(('```', '-', '*', '1.', '2.', '3.', '4.', '5.', '6.', '7.', '8.', '9.'))):
            cleaned_lines.append(line.rstrip())
    
    # 连接行并移除多个连续的空白行
    cleaned = '\n'.join(cleaned_lines)
    cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
    
    # 最终去除前导和尾随空白
    return cleaned.strip()


def extract_thinking_content(text):
    """
    从 LLM 响应中提取思考内容
    返回一个元组 (thinking_content, main_content)
    """
    if not text:
        return ("", "")
    
    # 查找所有思考标签及其内容
    thinking_pattern = r'<think(?:ing)?>.*?</think(?:ing)?>'
    thinking_matches = re.findall(thinking_pattern, text, flags=re.DOTALL | re.IGNORECASE)
    
    # 从标签中提取内容
    thinking_content = ""
    for match in thinking_matches:
        # 移除开始和结束标签
        content = re.sub(r'^<think(?:ing)?>', '', match, flags=re.IGNORECASE)
        content = re.sub(r'</think(?:ing)?>$', '', content, flags=re.IGNORECASE)
        if thinking_content:
            thinking_content += "\n\n"
        thinking_content += content.strip()
    
    # 移除思考标签后获取主要内容
    main_content = clean_llm_response(text)
    
    return (thinking_content, main_content)


def process_streaming_with_thinking(stream):
    """
    处理流式响应并分离思考内容的生成器
    以 SSE 格式输出数据，'delta' 表示常规内容，'thinking' 表示思考内容
    """
    content_buffer = ""
    in_thinking = False
    thinking_buffer = ""
    
    for chunk in stream:
        content = chunk.choices[0].delta.content
        if content:
            content_buffer += content
            
            # 处理缓冲区以检测和分离思考标签
            while True:
                if not in_thinking:
                    # 查找开始的思考标签
                    think_start = re.search(r'<think(?:ing)?>', content_buffer, re.IGNORECASE)
                    if think_start:
                        # 发送思考标签前的内容
                        before_thinking = content_buffer[:think_start.start()]
                        if before_thinking:
                            yield f"data: {json.dumps({'delta': before_thinking})}\n\n"
                        
                        # 开始捕获思考内容
                        in_thinking = True
                        content_buffer = content_buffer[think_start.end():]
                        thinking_buffer = ""
                    else:
                        # 未找到思考标签，发送累积的内容
                        if content_buffer:
                            yield f"data: {json.dumps({'delta': content_buffer})}\n\n"
                        content_buffer = ""
                        break
                else:
                    # 在思考标签内部，查找结束标签
                    think_end = re.search(r'</think(?:ing)?>', content_buffer, re.IGNORECASE)
                    if think_end:
                        # 捕获到结束标签前的思考内容
                        thinking_buffer += content_buffer[:think_end.start()]
                        
                        # 以特殊类型发送思考内容
                        if thinking_buffer.strip():
                            yield f"data: {json.dumps({'thinking': thinking_buffer.strip()})}\n\n"
                        
                        # 继续处理结束标签后的内容
                        in_thinking = False
                        content_buffer = content_buffer[think_end.end():]
                        thinking_buffer = ""
                    else:
                        # 仍在思考标签内部，累积内容
                        thinking_buffer += content_buffer
                        content_buffer = ""
                        break
    
    # 处理剩余内容
    if in_thinking and thinking_buffer:
        # 未闭合的思考标签 - 作为思考内容发送
        yield f"data: {json.dumps({'thinking': thinking_buffer.strip()})}\n\n"
    elif content_buffer:
        # 常规内容
        yield f"data: {json.dumps({'delta': content_buffer})}\n\n"
    
    # 发送流结束信号
    yield f"data: {json.dumps({'end_of_stream': True})}\n\n"


def call_llm_completion(messages, temperature=0.7, response_format=None, stream=False, max_tokens=None, extra_body=None, model_name=None, api_key=None):
    """
    集中处理LLM API调用的函数，包含完善的错误处理和数据记录功能。
    
    参数:
        messages: 消息字典列表，包含'role'和'content'键
        temperature: 采样温度值，范围0-1，数值越高随机性越强
        response_format: 可选的响应格式字典，例如 {"type": "json_object"}
        stream: 是否启用流式输出模式
        max_tokens: 可选参数，限制生成内容的最大token数量
        extra_body: 可选参数，用于传递额外的API参数字典
        model_name: 可选，覆盖默认模型名称
        api_key: 可选，覆盖默认API密钥（目前保留兼容性，实际使用全局client）
        
    返回:
        OpenAI的完成结果对象，如果启用流式输出则返回生成器对象
    """
    if client is None:
        raise ValueError("LLM客户端未初始化")
    
    model = model_name or TEXT_MODEL_NAME
    
    try:
        # 构建API调用基础参数
        completion_args = {
            "model": model,               # 使用的模型名称
            "messages": messages,          # 对话消息列表
            "temperature": temperature,    # 温度参数控制随机性
            "stream": stream               # 流式输出开关
        }
        
        # 如果指定了响应格式，添加到参数中
        if response_format:
            completion_args["response_format"] = response_format
            
        # 如果设置了最大token数，添加到参数中
        if max_tokens:
            completion_args["max_tokens"] = max_tokens
            
        # 新增：如果提供了额外参数，添加到API调用中
        if extra_body:
            completion_args["extra_body"] = extra_body
            
        # 执行API调用并返回结果
        return client.chat.completions.create(**completion_args)
        
    except Exception as e:
        # 记录错误日志并重新抛出异常
        logger.error(f"LLM API 调用失败（端点：{TEXT_MODEL_BASE_URL}，模型：{model}）：{e}")
        raise


def format_api_error_message(error_str):
    """
    格式化 API 错误消息，使其对用户更友好
    特别处理 token 超限错误并提供有用的建议
    """
    error_lower = error_str.lower()
    
    # 检查 token 超限错误
    if 'maximum context length' in error_lower and 'tokens' in error_lower:
        return "[摘要生成失败：转录内容过长，超出 AI 处理限制。请联系管理员尝试使用更大上下文窗口的模型，或在系统设置中调整 transcript_length_limit。]"
    
    # 检查其他常见 API 错误
    if 'rate limit' in error_lower:
        return "[摘要生成失败：API 请求频率超限。请稍后重试。]"
    
    if 'insufficient funds' in error_lower or 'quota exceeded' in error_lower:
        return "[摘要生成失败：API 配额已用完。请联系支持人员。]"
    
    if 'timeout' in error_lower:
        return "[摘要生成失败：请求超时。请重试。]"

    if 'connection error' in error_lower or 'connecterror' in error_lower or 'connection refused' in error_lower or 'actively refused' in error_lower:
        return "[摘要生成失败：无法连接到 LLM 服务。请检查 TEXT_MODEL_BASE_URL 对应的服务是否已启动并可访问。]"
    
    # 其他错误显示通用消息
    return f"[摘要生成失败：{error_str}]"


def extract_corrected_text(corrected_text, asr_text):
    """
    从模型回答中提取修正后的文本
    
    Args:
        corrected_text: 模型回答的完整内容
        asr_text: 原始ASR文本
    
    Returns:
        str: 提取到的修正文本或原始ASR文本
    """
    # 使用正则表达式匹配 '''内容''' 格式
    pattern = r'<result>(.*?)</result>'
    matches = re.findall(pattern, corrected_text, re.DOTALL)
    
    # 如果找到匹配项
    if matches:
        extracted_text = matches[0].strip()
        
        # 检查提取的文本长度是否在合理范围内
        # 不超过原始的1.2倍
        max_allowed_length = len(asr_text) * 1.2
        
        if len(extracted_text) <= max_allowed_length:
            logger.info(f"转录完成，已找到匹配内容")
            return extracted_text
    
    # 如果没有匹配到或者长度超标，返回原始ASR文本
    logger.info(f"未找到匹配内容或长度超标")
    return asr_text


def calculate_tokens(text):
    """计算文本的大致token数量"""
    # 保守估算：1个token ≈ 3个字符
    return len(text) // 3


def tokens_to_chars(tokens):
    """将token数量转换为字符数量"""
    return tokens * 3


def parse_speaker_segments(transcription_text):
    """
    解析转录文本，按发言人分组内容
    
    Args:
        transcription_text (str): 原始转录文本
    
    Returns:
        dict: 按发言人分组的对话内容 {speaker: [content1, content2, ...]}
    """
    speaker_segments = {}
    
    # 使用正则表达式匹配发言模式：[SPK_X]: 内容
    pattern = r'\[(SPK_\d+)\]:\s*(.*?)(?=\n\[SPK_|\n\n|\n$|$)'
    
    matches = re.findall(pattern, transcription_text, re.DOTALL)
    
    for speaker, content in matches:
        content = content.strip()
        if content:
            if speaker not in speaker_segments:
                speaker_segments[speaker] = []
            speaker_segments[speaker].append(content)
    
    logger.info(f"解析到 {len(speaker_segments)} 个发言人，共 {len(matches)} 段内容")
    return speaker_segments


def summarize_single_speaker(speaker, content):
    """
    总结单个发言人的内容
    
    Args:
        speaker (str): 发言人标识
        content (str): 发言内容
    
    Returns:
        str: 总结后的内容
    """
    from src.config import MAX_TOKENS_FOR_TRANSCRIPTION, SPEAKR_TOKENS_FOR_TRANSCRIPTION
    
    # 如果内容超过限制，进行截断
    content_tokens = calculate_tokens(content)
    max_content_length = tokens_to_chars(MAX_TOKENS_FOR_TRANSCRIPTION)
    if content_tokens > MAX_TOKENS_FOR_TRANSCRIPTION:
        logger.warning(f"发言人 {speaker} 内容过长（{len(content)} 字符），截断至 {max_content_length} 字符")
        content = content[:max_content_length] + "...[内容过长已截断]"
    
    prompt = f"""请总结以下发言人的对话内容，保留关键信息和主要观点：

发言人: {speaker}

对话内容:
{content}

请用简洁的语言总结这个发言人的主要观点、关键信息和重要发言，保持客观准确，不要添加额外评论。"""

    try:
        completion = call_llm_completion(
            messages=[
                {"role": "system", "content": "你是一个专业的会议记录总结助手，擅长提取对话中的关键信息。"},
                {"role": "user", "content": prompt}
            ],
            temperature=0.3,  # 较低温度确保准确性
            max_tokens=SPEAKR_TOKENS_FOR_TRANSCRIPTION   # 限制总结长度
        )
        
        summary = completion.choices[0].message.content.strip()
        return summary
        
    except Exception as e:
        logger.error(f"发言人 {speaker} 的 LLM 总结失败：{e}")
        # 回退策略：返回内容的前500个字符
        return content[:500] + "..."


def summarize_speaker_segments(speaker_segments):
    """
    对每个发言人的内容进行分段总结
    
    Args:
        speaker_segments (dict): 按发言人分组的对话内容
    
    Returns:
        dict: 每个发言人的总结内容 {speaker: summarized_content}
    """
    from src.config import SPEAKR_TOKENS_FOR_TRANSCRIPTION
    
    summarized_speakers = {}
    
    for speaker, segments in speaker_segments.items():
        try:
            # 合并该发言人的所有内容
            full_content = "\n".join(segments)
            estimated_tokens = calculate_tokens(full_content)
            # 如果内容不长，直接使用（避免过度总结）
            if estimated_tokens < SPEAKR_TOKENS_FOR_TRANSCRIPTION: 
                summarized_speakers[speaker] = full_content
                continue
            
            # 对长内容进行总结
            summary = summarize_single_speaker(speaker, full_content)
            summarized_speakers[speaker] = summary
            
            logger.info(f"已总结发言人 {speaker} 的内容：{len(full_content)} -> {len(summary)} 字符")
            
        except Exception as e:
            logger.error(f"总结发言人 {speaker} 的内容失败：{e}")
            # 如果总结失败，使用前500字符内容作为回退
            fallback_content = "\n".join(segments)  
            summarized_speakers[speaker] = fallback_content[:500]
    
    return summarized_speakers


def combine_speaker_summaries(summarized_speakers):
    """
    直接将各发言人的总结内容组合成原始格式
    
    Args:
        summarized_speakers (dict): 每个发言人的总结内容
    
    Returns:
        str: 组合后的文本，格式为 "[SPK_X]: 总结内容"
    """
    from src.config import MAX_TOKENS_FOR_TRANSCRIPTION, SPEAKR_TOKENS_FOR_TRANSCRIPTION
    
    lines = []
    
    for speaker, summary in summarized_speakers.items():
        # 如果总结内容仍然很长，进行适当截断
        content_tokens = calculate_tokens(summary)
        max_summary_length = SPEAKR_TOKENS_FOR_TRANSCRIPTION
        if content_tokens > max_summary_length:
            summary = summary[:max_summary_length] + "..."
        
        lines.append(f"[{speaker}]: {summary}")
    
    combined_text = "\n".join(lines)
    
    # 最终检查总长度，确保不超过token限制
    max_tokens = MAX_TOKENS_FOR_TRANSCRIPTION  # 约10000 tokens
    max_content_length = tokens_to_chars(MAX_TOKENS_FOR_TRANSCRIPTION)
    content_tokens = calculate_tokens(combined_text)
    if content_tokens > max_tokens:
        logger.warning(f"合并后的总结过长（{len(combined_text)} 字符），正在截断")
        combined_text = combined_text[:max_content_length] + "\n...[内容过长已截断]"
    
    return combined_text


def preprocess_long_transcription(transcription_text):
    """
    预处理过长的转录文本，避免token超限问题。
    如果文本过长，先分段总结每个发言人的内容，再进行最终总结。
    
    Args:
        transcription_text (str): 原始转录文本，格式为 "[SPK_X]: 发言内容\n[SPK_X]: 发言内容"
    
    Returns:
        str: 处理后的文本，如果未超限则返回原文本，如果超限则返回分段总结后的文本
    """
    from src.config import MAX_TOKENS_FOR_TRANSCRIPTION
    
    max_tokens = int(MAX_TOKENS_FOR_TRANSCRIPTION)
        
    # 如果转录文本为空或很短，直接返回
    if not transcription_text or len(transcription_text.strip()) < 1000:
        return transcription_text
    
    estimated_tokens = calculate_tokens(transcription_text)
    
    logger.info(f"转录文本预估 token 数：{estimated_tokens}，上限：{max_tokens}")
    
    # 如果估算的token数量未超过限制，直接返回原文本
    if estimated_tokens <= max_tokens:
        logger.info("转录文本在 token 限制内，使用原始文本")
        return transcription_text
    
    logger.info("转录文本超出 token 限制，开始分段总结流程")
    
    try:
        # 解析发言内容，按发言人分组
        speaker_segments = parse_speaker_segments(transcription_text)
        
        if not speaker_segments:
            logger.warning("未找到发言人分块，返回原始文本")
            return transcription_text
        
        # 对每个发言人的内容进行分段总结
        summarized_speakers = summarize_speaker_segments(speaker_segments)
        
        # 生成最终格式
        final_summary = combine_speaker_summaries(summarized_speakers)
        
        logger.info(f"分段总结完成，原始长度: {len(transcription_text)}, 总结后长度: {len(final_summary)}")
        return final_summary
        
    except Exception as e:
        logger.error(f"预处理长转录文本失败：{e}")
        # 如果预处理失败，返回原始文本的前面部分（安全回退）
        safe_length = tokens_to_chars(max_tokens)  # 保守估计字符数
        return transcription_text[:safe_length]
