# src/llm_interface.py
"""
LLM接口模块 - 支持多个LLM提供商
"""

import logging
import json
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any
import requests

from .utils import get_env_or_config

logger = logging.getLogger(__name__)


class LLMInterface(ABC):
    """LLM接口基类"""
    
    def __init__(self, api_key: str = None, model: str = None, **kwargs):
        """
        初始化LLM接口
        
        Args:
            api_key: API密钥
            model: 模型名称
            **kwargs: 其他参数（如temperature, max_tokens等）
        """
        self.api_key = api_key
        self.model = model
        self.extra_params = kwargs
    
    @abstractmethod
    def call(self, prompt: str) -> str:
        """
        调用LLM API
        
        Args:
            prompt: 提示词
            
        Returns:
            LLM的响应文本
        """
        pass
    
    @abstractmethod
    def call_json(self, prompt: str) -> Dict[str, Any]:
        """
        调用LLM API并获取JSON格式的响应
        
        Args:
            prompt: 提示词
            
        Returns:
            解析后的JSON字典
        """
        pass
    
    def validate_config(self) -> bool:
        """验证配置是否完整"""
        return bool(self.api_key and self.model)


class QwenLLM(LLMInterface):
    """阿里云千问LLM实现"""
    
    def __init__(self, api_key: str = None, model: str = "qwen-plus",
                 api_endpoint: str = None, **kwargs):
        """
        初始化千问LLM
        
        Args:
            api_key: 阿里云API密钥
            model: 模型名称（如qwen-plus, qwen-turbo）
            api_endpoint: API端点地址
            **kwargs: 其他参数
        """
        super().__init__(api_key, model, **kwargs)
        self.api_endpoint = api_endpoint or "https://dashscope.aliyuncs.com/compatible-mode/v1"
        self._check_dashscope_import()
    
    def _check_dashscope_import(self) -> None:
        """检查dashscope库是否已安装"""
        try:
            import dashscope
            self.dashscope = dashscope
        except ImportError:
            logger.error("dashscope库未安装，请运行: pip install dashscope")
            raise

    def _call_compatible_mode(self, prompt: str) -> str:
        """使用OpenAI兼容模式HTTP接口调用千问。"""
        endpoint = self.api_endpoint.rstrip("/") + "/chat/completions"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }
        payload = {
            "model": self.model,
            "messages": [{"role": "user", "content": prompt}],
            "temperature": self.extra_params.get("temperature", 0.3),
            "max_tokens": self.extra_params.get("max_tokens", 1024),
        }

        resp = requests.post(endpoint, headers=headers, json=payload, timeout=120)
        if resp.status_code != 200:
            raise Exception(f"兼容模式API调用失败: {resp.status_code} {resp.text[:300]}")

        data = resp.json()
        choices = data.get("choices", [])
        if not choices:
            raise Exception(f"兼容模式API返回无choices: {str(data)[:300]}")
        message = choices[0].get("message", {})
        content = message.get("content", "")
        if not content:
            raise Exception(f"兼容模式API返回空内容: {str(data)[:300]}")
        return content
    
    def call(self, prompt: str) -> str:
        """
        调用千问LLM
        
        Args:
            prompt: 提示词
            
        Returns:
            LLM的响应文本
        """
        if not self.validate_config():
            raise ValueError("LLM配置不完整（api_key或model未设置）")
        
        try:
            from dashscope import Generation
            
            # 设置API密钥
            self.dashscope.api_key = self.api_key
            
            # 构建请求参数 - dashscope 1.7.0 格式
            response = Generation.call(
                model=self.model,
                messages=[
                    {'role': 'user', 'content': prompt}
                ],
                result_format='message'  # 使用message格式
            )
            
            # 调试输出
            logger.debug(f"API响应类型: {type(response)}")
            logger.debug(f"API响应内容: {response}")
            
            # 处理响应
            if isinstance(response, dict):
                # dict格式响应
                status_code = response.get('status_code', 200)
                if status_code == 200:
                    output = response.get('output', {})
                    if 'choices' in output:
                        return output['choices'][0]['message']['content']
                    elif 'text' in output:
                        return output['text']
                    else:
                        # 尝试直接获取text
                        return output.get('text', str(output))
                else:
                    error_msg = response.get('message', response.get('code', 'Unknown error'))
                    logger.error(f"千问API返回错误: {error_msg}")
                    raise Exception(f"API调用失败: {error_msg}")
            else:
                # 对象格式响应
                if hasattr(response, 'status_code') and response.status_code == 200:
                    output = response.output
                    if hasattr(output, 'choices'):
                        return output.choices[0].message.content
                    elif hasattr(output, 'text'):
                        return output.text
                    else:
                        return str(output)
                elif hasattr(response, 'status_code'):
                    error_msg = getattr(response, 'message', str(response))
                    raise Exception(f"API调用失败: {error_msg}")
                else:
                    return str(response)
                
        except Exception as e:
            err_msg = str(e)
            # DashScope旧路径报url error时，回退到兼容模式接口。
            if "url error" in err_msg.lower() or "status: 400" in err_msg.lower():
                logger.warning("DashScope调用失败，回退兼容模式接口: %s", err_msg)
                return self._call_compatible_mode(prompt)
            logger.error(f"调用千问LLM失败: {e}")
            raise
    
    def call_json(self, prompt: str) -> Dict[str, Any]:
        """
        调用千问LLM并获取JSON格式响应
        
        Args:
            prompt: 提示词
            
        Returns:
            解析后的JSON字典
        """
        # 添加JSON格式要求到提示词
        json_prompt = prompt + "\n\n请确保响应是有效的JSON格式。"
        
        response = self.call(json_prompt)
        
        try:
            # 尝试解析JSON
            # 首先尝试直接解析
            return json.loads(response)
        except json.JSONDecodeError:
            # 尝试提取JSON代码块
            try:
                import re
                # 查找JSON代码块
                json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
                if json_match:
                    return json.loads(json_match.group(1))
                
                # 尝试查找任何JSON对象
                json_match = re.search(r'\{.*\}', response, re.DOTALL)
                if json_match:
                    return json.loads(json_match.group(0))
                    
            except Exception as e:
                logger.warning(f"无法从响应中提取JSON: {e}")
            
            # 如果都失败，返回错误信息
            logger.error(f"无法解析LLM响应为JSON: {response}")
            return {"error": "Failed to parse response as JSON", "raw_response": response}
