import requests
from abc import ABC
import logging
import json
from src.utils.logger_config import get_logger

# 导入 pypinyin 用于中文转拼音
try:
    import pypinyin
except ImportError:
    pypinyin = None
    logging.warning("pypinyin 模块未安装，将使用简化的命名方式")

logger = get_logger(__name__)


class DifyAPIError(Exception):
    """Dify API 错误异常类"""
    
    def __init__(self, status_code: int, error_code: str, message: str, request_data: dict = None):
        self.status_code = status_code
        self.error_code = error_code
        self.message = message
        self.request_data = request_data
        super().__init__(self.message)
    
    def __str__(self):
        return f"[{self.status_code}] {self.error_code}: {self.message}"
    
    def to_dict(self):
        return {
            "status_code": self.status_code,
            "error_code": self.error_code,
            "message": self.message
        }

def pinyin_to_camel(pinyin):
    """
    将中文名称转换为工具名称
    
    处理逻辑：
    1. 如果安装了 pypinyin，将中文转换为拼音，然后转为驼峰命名
    2. 如果未安装 pypinyin，将所有非字母数字字符替换为下划线
    3. 所有符号都会被替换成下划线
    
    示例:
        "你好啊" -> "tool_NiHaoA" (有pypinyin)
        "测试-工具" -> "tool_测试_工具" (无pypinyin)
        "Hello World!" -> "tool_Hello_World_" (无pypinyin)
    
    Args:
        pinyin: 输入的字符串（可能包含中文、英文、符号等）
    
    Returns:
        str: 格式化后的工具名称，以 "tool_" 开头
    """
    import re
    
    if pypinyin is None:
        # 如果 pypinyin 未安装，使用简化的命名方式
        # 将所有非字母数字字符（包括空格、符号等）替换为下划线
        cleaned = re.sub(r'[^\w]', '_', str(pinyin))
        # 移除连续的下划线
        cleaned = re.sub(r'_+', '_', cleaned)
        # 移除首尾的下划线
        cleaned = cleaned.strip('_')
        return "tool_" + cleaned if cleaned else "tool_unnamed"
    
    # 使用 pypinyin 转换中文为拼音
    pinyin_list = pypinyin.lazy_pinyin(pinyin)
    
    # 处理每个拼音单词
    processed_words = []
    for word in pinyin_list:
        # 将所有非字母数字字符替换为下划线
        cleaned_word = re.sub(r'[^\w]', '_', word)
        # 移除连续的下划线
        cleaned_word = re.sub(r'_+', '_', cleaned_word)
        # 移除首尾的下划线
        cleaned_word = cleaned_word.strip('_')
        
        if cleaned_word:
            # 首字母大写（驼峰命名）
            processed_words.append(cleaned_word.capitalize())
    
    # 拼接所有单词
    result = "".join(processed_words) if processed_words else "Unnamed"
    return "tool_" + result


class WorkflowDifyAPI(ABC):
    def __init__(self, base_url: str, dify_app_sks: list, user="pp666"):
        # dify configs
        self.dify_base_url = base_url
        self.dify_app_sks = dify_app_sks
        self.user = user

        # dify app infos
        dify_app_infos = []
        dify_app_params = []
        dify_app_metas = []
        for key in self.dify_app_sks:
            dify_app_infos.append(self.get_app_info(key))
            dify_app_params.append(self.get_app_parameters(key))
            dify_app_metas.append(self.get_app_meta(key))

        self.dify_app_infos = dify_app_infos
        self.dify_app_params = dify_app_params
        self.dify_app_metas = dify_app_metas
        self.dify_app_names = [x["name"] for x in dify_app_infos]

    def chat_message(
        self,
        api_key,
        inputs={},
        response_mode="streaming",
        conversation_id=None,
        userId="pp666",
        files=None,
    ):
        url = f"{self.dify_base_url}/workflows/run"
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }
        data = {
            "inputs": inputs,
            "response_mode": response_mode,
            "user": userId,
        }
        logger.info("Sending data to Dify API: %s", data)
        logger.info("Sending headers to Dify API: %s", headers)
        logger.info("Sending url to Dify API: %s", url)
        if conversation_id:
            data["conversation_id"] = conversation_id
        if files:
            files_data = self.file_parameter_pretreatment(files)
            if files_data and len(files_data) > 0:
                data["inputs"]["files"] = files_data[0]
            # For workflow API, we send files data in the JSON payload, not as multipart files
            response = requests.post(
                url, headers=headers, json=data, stream=response_mode == "streaming"
            )
        else:
            response = requests.post(
                url, headers=headers, json=data, stream=response_mode == "streaming"
            )
            logger.info(f"Response1:{data} {response.status_code} {response.reason}")

        # Add debugging for error responses
        if response.status_code != 200:
            logger.error(f"API request failed with status {response.status_code}")
            logger.error(f"Response content: {response.text}")
            logger.error(f"Request data: {data}")
            
            # 解析错误响应并抛出带有详细信息的异常
            try:
                error_data = response.json()
                error_message = error_data.get("message", response.text)
                error_code = error_data.get("code", "unknown_error")
            except json.JSONDecodeError:
                error_message = response.text
                error_code = "unknown_error"
            
            raise DifyAPIError(
                status_code=response.status_code,
                error_code=error_code,
                message=error_message,
                request_data=data
            )
        if response_mode == "streaming":
            def stream_generator():
                for line in response.iter_lines():
                    if line:
                        if line.startswith(b"data:"):
                            try:
                                json_data = json.loads(line[5:].decode("utf-8"))
                                yield json_data
                            except json.JSONDecodeError:
                                logger.error(f"Error decoding JSON: {line}")
            return stream_generator()
        else:
            return response.json()

    def upload_file(self, api_key, file_path, user="pp666"):
        url = f"{self.dify_base_url}/files/upload"
        headers = {"Authorization": f"Bearer {api_key}"}
        data = {"user": user}
        
        with open(file_path, "rb") as f:
            files = {"file": f}
            response = requests.post(url, headers=headers, files=files, data=data)
        
        response.raise_for_status()
        return response.json()
    def upload_file_remote_url(self, file_url):
        from src.utils.upload_file import upload_file_from_url
        base_url = self.dify_base_url
        api_key = self.dify_app_sks[0]
        return upload_file_from_url(file_url, base_url, api_key)

    def file_parameter_pretreatment(self, files):
        """
        文件参数预处理方法

        传入的"files"数据结构是这样的: [
            {
                "type": "image",
                "transfer_method": "remote_url",
                "url": "http://example.com/image.jpg"
            }
        ]

        处理逻辑：
        1. 遍历files列表中的每个文件对象
        2. 对于transfer_method为"remote_url"的文件，调用upload_file_remote_url方法
        3. 将返回的对象的id字段存入原对象的upload_file_id字段
        4. 设置transfer_method为"local_file"（因为已经上传到Dify服务器）
        5. 返回处理好的files列表

        Args:
            files (list): 文件列表，每个元素包含type、transfer_method、url等字段

        Returns:
            list: 处理后的文件列表，每个文件对象包含upload_file_id和transfer_method字段
        """
        if not files or not isinstance(files, list):
            logger.warning("文件参数为空或格式不正确")
            return files

        processed_files = []

        for file_obj in files:
            # 创建文件对象的副本，避免修改原始数据
            processed_file = file_obj.copy()

            # 检查是否需要处理远程URL文件
            if (processed_file.get("transfer_method") == "remote_url" and
                processed_file.get("url")):

                try:
                    logger.info(f"开始上传远程文件: {processed_file['url']}")
                    
                    # 调用upload_file_remote_url方法：下载文件并上传到Dify
                    upload_result = self.upload_file_remote_url(processed_file["url"])

                    # 将返回的对象的id存入upload_file_id字段
                    if upload_result and "id" in upload_result:
                        processed_file["upload_file_id"] = upload_result["id"]
                        # 修改transfer_method为local_file，因为文件已经上传到Dify服务器
                        processed_file["transfer_method"] = "local_file"
                        # 移除url字段，因为已经不需要了
                        processed_file.pop("url", None)
                        
                        logger.info(f"文件上传成功 - ID: {upload_result['id']}, "
                                  f"名称: {upload_result.get('name', 'N/A')}, "
                                  f"大小: {upload_result.get('size', 'N/A')} bytes")
                    else:
                        logger.error(f"文件上传失败，未获取到有效的文件ID，响应: {upload_result}")
                        processed_file["upload_error"] = "未获取到有效的文件ID"

                except Exception as e:
                    logger.error(f"文件上传过程中发生错误: {str(e)}", exc_info=True)
                    # 记录错误信息，但继续处理其他文件
                    processed_file["upload_error"] = str(e)

            elif processed_file.get("transfer_method") == "local_file":
                # 如果已经是local_file，确保有upload_file_id
                if not processed_file.get("upload_file_id"):
                    logger.warning("local_file类型的文件缺少upload_file_id字段")
            
            processed_files.append(processed_file)

        logger.info(f"文件预处理完成，共处理 {len(processed_files)} 个文件")
        return processed_files
    def stop_response(self, api_key, task_id, user="pp666"):

        url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }
        data = {"user": user}
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        return response.json()

    def get_app_info(self, api_key, user="pp666"):

        url = f"{self.dify_base_url}/info"
        headers = {"Authorization": f"Bearer {api_key}"}
        params = {"user": user}
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()

        response_map = response.json()

        # 翻译工具名称
        tool_name = response_map.get("name")
        if tool_name:
            # translated_name = TranslationService.translate_tool_name(tool_name)
            translated_name = pinyin_to_camel(tool_name)
            response_map["name"] = translated_name

        # 翻译工具描述
        # tool_description = response_map.get("description")
        # if tool_description:
        #     translated_description = TranslationService.translate_tool_description(
        #         tool_description
        #     )
        #     response_map["description"] = (
        #         f"{tool_description} ({translated_description})"
        #     )

        return response_map

    def get_app_parameters(self, api_key, user="pp666"):
        url = f"{self.dify_base_url}/parameters"
        headers = {"Authorization": f"Bearer {api_key}"}
        params = {"user": user}
        
        logger.info(f"调用 /parameters API: {url}")
        logger.info(f"请求头: {headers}")
        logger.info(f"请求参数: {params}")
        
        response = requests.get(url, headers=headers, params=params)
        
        logger.info(f"/parameters API 响应状态码: {response.status_code}")
        
        response.raise_for_status()
        
        response_data = response.json()
        logger.info(f"/parameters API 响应数据: {response_data}")
        
        return response_data

    def get_app_meta(self, api_key, user="pp666"):
        url = f"{self.dify_base_url}/meta"
        headers = {"Authorization": f"Bearer {api_key}"}
        params = {"user": user}
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()
        return response.json()



if __name__ == "__main__":
    dify_api = WorkflowDifyAPI(
        "https://ops.lzwcai.com/v1",
        ["app-ZmLuBlRmViseUdOonqLyNSku", "app-AHjfp8k4nawQSJi0us8x3J5Q"],
    )
    dify_api.upload_file_remote_url("http://192.168.2.236:9000/lzwcai/upload/2025-07-29/34b28da03f3c43b0921ba1b76857bbc0/34b28da03f3c43b0921ba1b76857bbc0.JPG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20250729%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250729T075242Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=80f58d37c3bd52fb2b25efa36b5df0d73c8da7c773e7a7066dd6563710c619d6")
