#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：LZKB 
@File    ：llm.py
@Author  ：Brian Yang
@Date    ：5/12/24 7:44 AM 
"""
import json
from typing import Dict, Any

from langchain_core.language_models import LanguageModelInput
from langchain_core.messages import AIMessage

from models_provider.base_model_provider import LZKBBaseModel
from models_provider.impl.base_chat_open_ai import BaseChatOpenAI


class DeepSeekChatModel(LZKBBaseModel, BaseChatOpenAI):

    @staticmethod
    def is_cache_model():
        return False

    @staticmethod
    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):
        optional_params = LZKBBaseModel.filter_optional_params(model_kwargs)

        deepseek_chat_open_ai = DeepSeekChatModel(
            model=model_name,
            openai_api_base=model_credential.get('api_base') or 'https://api.deepseek.com',
            openai_api_key=model_credential.get('api_key'),
            **optional_params,
        )
        return deepseek_chat_open_ai

    def _get_request_payload(
            self,
            input_: LanguageModelInput,
            *,
            stop: list[str] | None = None,
            **kwargs: Any,
    ) -> dict:
        # Get original messages to preserve reasoning_content before base conversion
        messages = self._convert_input(input_).to_messages()
        # Store reasoning_content for AIMessages with tool_calls
        # According to DeepSeek API docs, reasoning_content is REQUIRED when tool_calls
        # are present during the tool invocation process (within same question/turn).
        # See: https://api-docs.deepseek.com/guides/thinking_mode#tool-calls
        reasoning_content_map = {}
        for i, msg in enumerate(messages):
            if (
                    isinstance(msg, AIMessage)
                    and (msg.tool_calls or msg.invalid_tool_calls)
                    and (reasoning := msg.additional_kwargs.get("reasoning_content"))
            ):
                reasoning_content_map[i] = reasoning

        payload = super()._get_request_payload(input_, stop=stop, **kwargs)

        # Restore reasoning_content for assistant messages with tool_calls
        # This is required by DeepSeek API - missing it causes 400 error
        if "messages" in payload and reasoning_content_map:
            for i, message in enumerate(payload["messages"]):
                if (
                        i in reasoning_content_map
                        and message.get("role") == "assistant"
                        and message.get("tool_calls")
                ):
                    message["reasoning_content"] = reasoning_content_map[i]

        # Apply DeepSeek-specific message formatting
        for message in payload["messages"]:
            if message["role"] == "tool" and isinstance(message["content"], list):
                message["content"] = json.dumps(message["content"])
            elif message["role"] == "assistant" and isinstance(
                    message["content"], list
            ):
                # DeepSeek API expects assistant content to be a string, not a list.
                # Extract text blocks and join them, or use empty string if none exist.
                text_parts = [
                    block.get("text", "")
                    for block in message["content"]
                    if isinstance(block, dict) and block.get("type") == "text"
                ]
                message["content"] = "".join(text_parts) if text_parts else ""
        return payload
