import json
from abc import ABC, abstractmethod
from openai import OpenAIError as OriginalError
from typing import List, Union, Optional

import uuid, time, openai, random, requests
from openai._models import BaseModel as OpenAIObject

class Message(OpenAIObject):
    def __init__(
        self,
        content="default",
        role="assistant",
        logprobs=None,
        function_call=None,
        **params
    ):
        super(Message, self).__init__(**params)
        self.content = content
        self.role = role
        self._logprobs = logprobs
        if function_call:
            self.function_call = function_call

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)


class Delta(OpenAIObject):
    def __init__(self, content=None, role=None, thought_signature=None, **params):
        super(Delta, self).__init__(**params)
        if content is not None:
            self.content = content
        if role:
            self.role = role
        if thought_signature:
            self.thought_signature = thought_signature

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)


class Choices(OpenAIObject):
    def __init__(self, finish_reason=None, index=0, message=None, **params):
        super(Choices, self).__init__(**params)
        if finish_reason:
            self.finish_reason = map_finish_reason(finish_reason)
        else:
            self.finish_reason = "stop"
        self.index = index
        if message is None:
            self.message = Message(content=None)
        else:
            self.message = message

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)

class Usage(OpenAIObject):
    def __init__(
        self, prompt_tokens=None, completion_tokens=None, total_tokens=None, **params
    ):
        super(Usage, self).__init__(**params)
        if prompt_tokens:
            self.prompt_tokens = prompt_tokens
        if completion_tokens:
            self.completion_tokens = completion_tokens
        if total_tokens:
            self.total_tokens = total_tokens
        
    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)

class Context(OpenAIObject):
    def __init__(self, id=None, content=None, score=None, **params):
        super(Context, self).__init__(**params)
        if id:
            self.id = id
        if content:
            self.content = content
        if score:
            self.score = score

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)

class StreamingChoices(OpenAIObject):
    def __init__(
        self, finish_reason=None, index=0, delta: Optional[Delta] = None, **params
    ):
        super(StreamingChoices, self).__init__(**params)

        self.finish_reason = finish_reason
        self.index = index
        if delta:
            self.delta = delta
        else:
            self.delta = Delta()

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)


def _generate_id():  # private helper function
    return "chatcmpl-" + str(uuid.uuid4())


def map_finish_reason(
    finish_reason: str,
):  # openai supports 5 stop sequences - 'stop', 'length', 'function_call', 'content_filter', 'null'
    # anthropic mapping
    if finish_reason == "stop_sequence":
        return "stop"
    return finish_reason


class ModelResponse(OpenAIObject):
    id: str
    """A unique identifier for the completion."""

    choices: List[Union[Choices, StreamingChoices]]
    """The list of completion choices the model generated for the input prompt."""

    created: int
    """The Unix timestamp (in seconds) of when the completion was created."""

    model: Optional[str] = None
    """The model used for completion."""

    object: str
    """The object type, which is always "text_completion" """

    system_fingerprint: Optional[str] = None
    """This fingerprint represents the backend configuration that the model runs with.

    Can be used in conjunction with the `seed` request parameter to understand when
    backend changes have been made that might impact determinism.
    """

    usage: Optional[Usage] = None
    """Usage statistics for the completion request."""  

    _hidden_params: dict = {}

    def __init__(
        self,
        id=None,
        choices=None,
        created=None,
        model=None,
        object=None,
        system_fingerprint=None,
        usage=None,
        conversation_id=None,
        stream=False,
        response_ms=None,
        hidden_params=None,
        **params
    ):
        if stream:
            object = "chat.completion.chunk"
        else:
            object = "chat.completion"
        if id is None:
            id = _generate_id()
        if created is None:
            created = int(time.time())
        if response_ms:
            _response_ms = response_ms
        if not usage:
            usage = Usage()
        
        if hidden_params:
            self._hidden_params = hidden_params

        filtered_params = {}      
        if conversation_id:
            filtered_params['conversation_id'] = conversation_id

        # 只有当 conversation_id 不为 None 时才添加到 filtered_params
        if 'conversation_id'in params and conversation_id is not None:
            filtered_params['conversation_id'] = conversation_id
        
        super().__init__(
            id=id,
            choices=choices,
            created=created,
            model=model,
            object=object,
            system_fingerprint=system_fingerprint,
            usage=usage,
            **filtered_params
        )

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)

class EmbeddingResponse(OpenAIObject):
    def __init__(
        self,
        id=None,
        choices=None,
        created=None,
        model=None,
        usage=None,
        stream=False,
        response_ms=None,
        **params
    ):
        self.object = "list"
        if response_ms:
            self._response_ms = response_ms
        else:
            self._response_ms = None
        self.data = []
        self.model = model

    def to_dict_recursive(self):
        d = super().to_dict_recursive()
        return d

def generate_unique_uid():
    # Get the current time in microseconds
    microseconds = int(time.time() * 1000000)
    
    # Get a random number between 0 and 0xFFFF
    rand_num = random.randint(0, 0xFFFF)
    
    # Combine them to get a unique ID
    unique_uid = f"{microseconds:x}{rand_num:04x}"
    
    return unique_uid

def check_object_input_support(provider):
    not_supported_providers = ["wenxin", "baichuan", "minimax", "xunfei", "tiangong", "lingyi", "fastgpt", "doubao", "moonshot"]
    if provider in not_supported_providers:
        return "NONE"
    elif provider == "coze":
        return "PARTIAL"
    else:
        return "FULL"

def check_file_input_support(provider, model):
    # supported_providers = ["openai", "anthropic", "claude"]
    if provider == "coze":
        return "PARTIAL"
    elif provider in ["gemini", "anthropic"]:
        return "FULL"
    else:
        return "PARTIAL"

def check_vision_input_support(provider, model):
    # supported_providers = ['zhipuai']
    if provider == "coze":
        return "PARTIAL"
    elif provider == "dify":
        return "FULL"
    else:
        # 默认支持，不做限制
        return "FULL"

def check_video_input_support(provider, model):
    # supported_providers = ['zhipuai']
    if provider == "gemini":
        return "FULL"
    else:
        return "PARTIAL"

def check_audio_input_support(provider, model):
    if provider == "gemini":
        return "FULL"
    else:
        return "PARTIAL"

def reformat_object_content(messages, reformat=False, reformat_image=False, reformat_file=False, reformat_video=False, reformat_audio=False):
    formatted_messages = []
    for message in messages:
        # 保留原始消息除了content以外的所有字段（包括tool_call_id和name）
        new_formatted_message = {k: v for k, v in message.items() if k != 'content'}
        if isinstance(message.get("content"), list):
            new_formatted_message["content"] = []
            message_contents = message.get("content")   
            to_append_text = ""
            for content in message_contents:
                if not isinstance(content, dict):
                    return False
                content_type = content.get("type")
                if content_type == "text":
                    if isinstance(content.get("text"), str):
                        new_formatted_message["content"].append(content)
                elif content_type == "audio_url":
                    if not reformat_audio:
                        new_formatted_message["content"].append(content)
                        continue
                    elif content.get("audio_url") and content.get("audio_url").get("url"):
                        if reformat_audio == 1:
                            to_append_text += f"![audio]({content.get('audio_url').get('url')})"
                        elif reformat_audio == 2:
                            # 转换音频为inline_data格式 (适用于 Gemini/litellm)
                            audio_url = content.get("audio_url").get("url")
                            try:
                                import base64
                                import requests
                                
                                # 检查是否已经是base64格式
                                if "base64," in audio_url:
                                    # 已经是base64格式，直接使用
                                    audio_data = audio_url
                                else:
                                    # 获取音频数据
                                    response = requests.get(audio_url)
                                    audio_content = response.content
                                    
                                    # 确定音频MIME类型
                                    # 支持的音频格式: mp3, wav, ogg, flac, m4a
                                    content_type_header = response.headers.get('Content-Type', 'audio/mpeg')
                                    
                                    # 如果无法从header获取，尝试从URL扩展名推断
                                    if content_type_header == 'audio/mpeg' and not audio_url.endswith('.mp3'):
                                        ext_mapping = {
                                            '.wav': 'audio/wav',
                                            '.ogg': 'audio/ogg',
                                            '.flac': 'audio/flac',
                                            '.m4a': 'audio/m4a'
                                        }
                                        for ext, mime in ext_mapping.items():
                                            if audio_url.lower().endswith(ext):
                                                content_type_header = mime
                                                break
                                    
                                    # 转换为base64
                                    encoded_audio = base64.b64encode(audio_content).decode('utf-8')
                                    audio_data = f"data:{content_type_header};base64,{encoded_audio}"
                                
                                # 创建inline_data格式 (litellm标准格式)
                                new_content = {
                                    "type": "file",
                                    "file": {
                                        "file_data": audio_data
                                    }
                                }
                                new_formatted_message["content"].append(new_content)
                            except Exception as e:
                                # 如果获取失败，则使用原始URL格式
                                to_append_text += f"![audio]({audio_url})"
                    else:
                        return False
                elif content_type in ["image_url","image"]:
                    if not reformat_image:
                        new_formatted_message["content"].append(content)
                        continue
                    elif content.get("image_url") and content.get("image_url").get("url"):
                        to_append_text += f"![image]({content.get('image_url').get('url')})"
                    else:
                        return False
                elif content_type == "video_url":
                    if not reformat_video:
                        new_formatted_message["content"].append(content)
                        continue
                    elif content.get("video_url") and content.get("video_url").get("url"):
                        if reformat_video == 1:
                            to_append_text += f"![video]({content.get('video_url').get('url')})"
                        elif reformat_video == 2:
                            # 转换视频为inline_data格式 (适用于 Gemini/litellm)
                            video_url = content.get("video_url").get("url")
                            try:
                                import base64
                                import requests
                                
                                # 检查是否已经是base64格式
                                if "base64," in video_url:
                                    # 已经是base64格式，直接使用
                                    video_data = video_url
                                else:
                                    # 获取视频数据
                                    response = requests.get(video_url)
                                    video_content = response.content
                                    
                                    # 确定视频MIME类型
                                    # 支持的视频格式: mp4, mpeg, mov, avi, x-flv, mpg, webm, wmv, 3gpp
                                    content_type_header = response.headers.get('Content-Type', 'video/mp4')
                                    
                                    # 如果无法从header获取，尝试从URL扩展名推断
                                    if content_type_header == 'video/mp4' and not video_url.endswith('.mp4'):
                                        ext_mapping = {
                                            '.mpeg': 'video/mpeg',
                                            '.mov': 'video/mov',
                                            '.avi': 'video/avi',
                                            '.flv': 'video/x-flv',
                                            '.mpg': 'video/mpg',
                                            '.webm': 'video/webm',
                                            '.wmv': 'video/wmv',
                                            '.3gpp': 'video/3gpp',
                                            '.3gp': 'video/3gpp'
                                        }
                                        for ext, mime in ext_mapping.items():
                                            if video_url.lower().endswith(ext):
                                                content_type_header = mime
                                                break
                                    
                                    # 转换为base64
                                    encoded_video = base64.b64encode(video_content).decode('utf-8')
                                    video_data = f"data:{content_type_header};base64,{encoded_video}"
                                
                                # 创建inline_data格式 (litellm标准格式)
                                new_content = {
                                    "type": "file",
                                    "file": {
                                        "file_data": video_data
                                    }
                                }
                                new_formatted_message["content"].append(new_content)
                            except Exception as e:
                                # 如果获取失败，则使用原始URL格式
                                to_append_text += f"![video]({video_url})"
                    else:
                        return False
                elif content_type == "file_url":
                    if not reformat_file:
                        new_formatted_message["content"].append(content)
                        continue
                    elif content.get("file_url") and content.get("file_url").get("url"):
                        if reformat_file == 1:
                            to_append_text += f"[file]({content.get('file_url').get('url')})"
                        elif reformat_file == 2:
                            # 只转换pdf文件
                            if content.get("file_url").get("url").endswith(".pdf"):
                                # 将文件转换为base64
                                file_url = content.get("file_url").get("url")
                                # 从URL获取文件数据
                                try:
                                    import base64
                                    import requests
                                    from urllib.parse import urlparse
                                    
                                    # 检查是否已经是base64格式
                                    if "base64," in file_url:
                                        # 已经是base64格式，直接使用
                                        file_data = file_url
                                    else:
                                        # 获取content中的

                                        # 获取文件数据
                                        response = requests.get(file_url)
                                        file_content = response.content
                                        
                                        # 确定文件类型
                                        content_type = response.headers.get('Content-Type', 'application/octet-stream')
                                        
                                        # 转换为base64
                                        encoded_file = base64.b64encode(file_content).decode('utf-8')
                                        file_data = f"data:{content_type};base64,{encoded_file}"
                                    
                                    # 创建所需格式
                                    new_content = {
                                        "type": "file",
                                        "file": {
                                            "file_data": file_data
                                        }
                                    }
                                    new_formatted_message["content"].append(new_content)
                                except Exception as e:
                                    # 如果获取失败，则使用原始URL
                                    to_append_text += f"[file]({file_url})"
                            else:
                                file_url = content.get("file_url").get("url")
                                to_append_text += f"[file]({file_url})"
                    else:
                        return False
            formatted_messages.append(new_formatted_message)   

            if to_append_text:
                # 将to_append_text添加到formatted_messages中的text类型文本中
                for content in formatted_messages[-1]["content"]:
                    if content.get("type") == "text":
                        content["text"] += to_append_text
        else:
            formatted_messages.append(message)
    return formatted_messages

class Function(OpenAIObject):
    def __init__(
        self,
        name=None,
        description=None,
        parameters=None,
        **params
    ):
        super(Function, self).__init__(**params)
        if name:
            self.name = name
        if description:
            self.description = description
        if parameters:
            self.parameters = parameters

    def __contains__(self, key):
        # Define custom behavior for the 'in' operator
        return hasattr(self, key)

    def get(self, key, default=None):
        # Custom .get() method to access attributes with a default value if the attribute doesn't exist
        return getattr(self, key, default)

    def __getitem__(self, key):
        # Allow dictionary-style access to attributes
        return getattr(self, key)

    def __setitem__(self, key, value):
        # Allow dictionary-style assignment of attributes
        setattr(self, key, value)

class ChatCompletionMessageToolCall(OpenAIObject):
    def __init__(
        self,
        id=None,
        type=None,
        function=None,
        **params
    ):
        super(ChatCompletionMessageToolCall, self).__init__(**params)
        if id:
            self.id = id
        if type:
            self.type = type
        if function:
            self.function = function

    def __contains__(self, key):
        return hasattr(self, key)

    def get(self, key, default=None):
        return getattr(self, key, default)

    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)