from __future__ import annotations

import typing
import json


from langbot.pkg.provider import runner
from langbot.pkg.core import app
import langbot_plugin.api.entities.builtin.provider.message as provider_message
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
from langbot.libs.weknora_api import client, errors

_MAX_WEKNORA_GENERATED_CHARS = 1024 * 1024


def _append_bounded(current: str, addition: typing.Any) -> str:
    addition = str(addition or '')
    if len(current) + len(addition) > _MAX_WEKNORA_GENERATED_CHARS:
        raise errors.WeKnoraAPIError('WeKnora response exceeds the runtime limit')
    return current + addition


@runner.runner_class('weknora-api')
class WeKnoraAPIRunner(runner.RequestRunner):
    """WeKnora API 对话请求器"""

    weknora_client: client.AsyncWeKnoraClient

    def __init__(self, ap: app.Application, pipeline_config: dict):
        super().__init__(ap, pipeline_config)

        valid_app_types = ['chat', 'agent']
        if self.pipeline_config['ai']['weknora-api']['app-type'] not in valid_app_types:
            raise errors.WeKnoraAPIError(
                f'不支持的 WeKnora 应用类型: {self.pipeline_config["ai"]["weknora-api"]["app-type"]}'
            )

        api_key = self.pipeline_config['ai']['weknora-api'].get('api-key', '').strip()
        if not api_key:
            raise errors.WeKnoraAPIError(
                'WeKnora API Key 未配置，请在流水线的 WeKnora API 配置中填入 API Key '
                '(从 WeKnora 前端 设置 → API Keys 生成)'
            )

        base_url = self.pipeline_config['ai']['weknora-api'].get('base-url', '').strip()
        if not base_url:
            raise errors.WeKnoraAPIError('WeKnora Base URL 未配置，请填入服务器地址，例如 http://localhost:8080/api/v1')

        self.weknora_client = client.AsyncWeKnoraClient(
            api_key=api_key,
            base_url=base_url,
        )

    async def _extract_plain_text(self, query: pipeline_query.Query) -> str:
        """从用户消息中提取纯文本内容"""
        plain_text = ''
        if isinstance(query.user_message.content, str):
            plain_text = query.user_message.content
        elif isinstance(query.user_message.content, list):
            for ce in query.user_message.content:
                if ce.type == 'text':
                    plain_text += ce.text

        if not plain_text:
            plain_text = self.pipeline_config['ai']['weknora-api'].get('base-prompt', '')

        return plain_text

    async def _ensure_session(self, query: pipeline_query.Query) -> str:
        """确保会话存在，如果不存在则创建"""
        session_id = query.session.using_conversation.uuid or ''

        if not session_id:
            user_tag = f'{query.session.launcher_type.value}_{query.session.launcher_id}'
            session_id = await self.weknora_client.create_session(title=f'IM Chat - {user_tag}')
            query.session.using_conversation.uuid = session_id

        return session_id

    async def _agent_chat_messages(
        self, query: pipeline_query.Query
    ) -> typing.AsyncGenerator[provider_message.Message, None]:
        """调用 Agent 智能对话（非流式聚合输出）"""
        session_id = await self._ensure_session(query)
        plain_text = await self._extract_plain_text(query)
        user_tag = f'{query.session.launcher_type.value}_{query.session.launcher_id}'

        config = self.pipeline_config['ai']['weknora-api']
        agent_id = config.get('agent-id', 'builtin-smart-reasoning')
        knowledge_base_ids = config.get('knowledge-base-ids', [])
        web_search_enabled = config.get('web-search-enabled', False)
        timeout = config.get('timeout', 120)

        full_answer = ''
        chunk = None

        async for chunk in self.weknora_client.agent_chat(
            session_id=session_id,
            query=plain_text,
            user=user_tag,
            agent_id=agent_id,
            knowledge_base_ids=knowledge_base_ids,
            web_search_enabled=web_search_enabled,
            timeout=timeout,
        ):
            self.ap.logger.debug('weknora-agent-chunk: ' + str(chunk)[:1000])

            response_type = chunk.get('response_type', '')
            content = chunk.get('content', '')

            if response_type == 'tool_call':
                # 工具调用
                tool_data = chunk.get('data', {})
                tool_name = tool_data.get('tool_name', '')
                if tool_name:
                    yield provider_message.Message(
                        role='assistant',
                        tool_calls=[
                            provider_message.ToolCall(
                                id=chunk.get('id', ''),
                                type='function',
                                function=provider_message.FunctionCall(
                                    name=tool_name,
                                    arguments=json.dumps(tool_data.get('arguments', {})),
                                ),
                            )
                        ],
                    )

            elif response_type == 'answer':
                if content:
                    full_answer = _append_bounded(full_answer, content)

            elif response_type == 'error':
                raise errors.WeKnoraAPIError(f'WeKnora 服务错误: {content}')

        if chunk is None:
            raise errors.WeKnoraAPIError('WeKnora API 没有返回任何响应，请检查网络连接和API配置')

        if full_answer:
            yield provider_message.Message(
                role='assistant',
                content=full_answer,
            )

    async def _chat_messages(
        self, query: pipeline_query.Query
    ) -> typing.AsyncGenerator[provider_message.Message, None]:
        """调用知识库 RAG 问答（非流式聚合输出）"""
        session_id = await self._ensure_session(query)
        plain_text = await self._extract_plain_text(query)
        user_tag = f'{query.session.launcher_type.value}_{query.session.launcher_id}'

        config = self.pipeline_config['ai']['weknora-api']
        agent_id = config.get('agent-id', 'builtin-quick-answer')
        knowledge_base_ids = config.get('knowledge-base-ids', [])
        timeout = config.get('timeout', 120)

        full_answer = ''
        chunk = None

        async for chunk in self.weknora_client.knowledge_chat(
            session_id=session_id,
            query=plain_text,
            user=user_tag,
            agent_id=agent_id,
            knowledge_base_ids=knowledge_base_ids,
            timeout=timeout,
        ):
            self.ap.logger.debug('weknora-chat-chunk: ' + str(chunk)[:1000])

            response_type = chunk.get('response_type', '')
            content = chunk.get('content', '')

            if response_type == 'answer':
                if content:
                    full_answer = _append_bounded(full_answer, content)

            elif response_type == 'error':
                raise errors.WeKnoraAPIError(f'WeKnora 服务错误: {content}')

        if chunk is None:
            raise errors.WeKnoraAPIError('WeKnora API 没有返回任何响应，请检查网络连接和API配置')

        if full_answer:
            yield provider_message.Message(
                role='assistant',
                content=full_answer,
            )

    async def _agent_chat_messages_chunk(
        self, query: pipeline_query.Query
    ) -> typing.AsyncGenerator[provider_message.MessageChunk, None]:
        """调用 Agent 智能对话（流式输出）"""
        session_id = await self._ensure_session(query)
        plain_text = await self._extract_plain_text(query)
        user_tag = f'{query.session.launcher_type.value}_{query.session.launcher_id}'

        config = self.pipeline_config['ai']['weknora-api']
        agent_id = config.get('agent-id', 'builtin-smart-reasoning')
        knowledge_base_ids = config.get('knowledge-base-ids', [])
        web_search_enabled = config.get('web-search-enabled', False)
        timeout = config.get('timeout', 120)

        pending_answer = ''
        message_idx = 0
        is_final = False
        chunk = None

        async for chunk in self.weknora_client.agent_chat(
            session_id=session_id,
            query=plain_text,
            user=user_tag,
            agent_id=agent_id,
            knowledge_base_ids=knowledge_base_ids,
            web_search_enabled=web_search_enabled,
            timeout=timeout,
        ):
            self.ap.logger.debug('weknora-agent-chunk: ' + str(chunk)[:1000])

            response_type = chunk.get('response_type', '')
            content = chunk.get('content', '')
            done = chunk.get('done', False)

            if response_type == 'tool_call':
                tool_data = chunk.get('data', {})
                tool_name = tool_data.get('tool_name', '')
                if tool_name:
                    message_idx += 1
                    yield provider_message.MessageChunk(
                        role='assistant',
                        tool_calls=[
                            provider_message.ToolCall(
                                id=chunk.get('id', ''),
                                type='function',
                                function=provider_message.FunctionCall(
                                    name=tool_name,
                                    arguments=json.dumps(tool_data.get('arguments', {})),
                                ),
                            )
                        ],
                    )

            elif response_type == 'answer':
                message_idx += 1
                if content:
                    pending_answer = _append_bounded(pending_answer, content)

                if done:
                    is_final = True

                # 每 8 个 chunk 输出一次，或最终输出
                if message_idx % 8 == 0 or is_final:
                    yield provider_message.MessageChunk(
                        role='assistant',
                        content=pending_answer,
                        is_final=is_final,
                    )

            elif response_type == 'error':
                raise errors.WeKnoraAPIError(f'WeKnora 服务错误: {content}')

        if chunk is None:
            raise errors.WeKnoraAPIError('WeKnora API 没有返回任何响应，请检查网络连接和API配置')

        # 确保最终消息已发出
        if not is_final and pending_answer:
            yield provider_message.MessageChunk(
                role='assistant',
                content=pending_answer,
                is_final=True,
            )

    async def _chat_messages_chunk(
        self, query: pipeline_query.Query
    ) -> typing.AsyncGenerator[provider_message.MessageChunk, None]:
        """调用知识库 RAG 问答（流式输出）"""
        session_id = await self._ensure_session(query)
        plain_text = await self._extract_plain_text(query)
        user_tag = f'{query.session.launcher_type.value}_{query.session.launcher_id}'

        config = self.pipeline_config['ai']['weknora-api']
        agent_id = config.get('agent-id', 'builtin-quick-answer')
        knowledge_base_ids = config.get('knowledge-base-ids', [])
        timeout = config.get('timeout', 120)

        pending_answer = ''
        message_idx = 0
        is_final = False
        chunk = None

        async for chunk in self.weknora_client.knowledge_chat(
            session_id=session_id,
            query=plain_text,
            user=user_tag,
            agent_id=agent_id,
            knowledge_base_ids=knowledge_base_ids,
            timeout=timeout,
        ):
            self.ap.logger.debug('weknora-chat-chunk: ' + str(chunk)[:1000])

            response_type = chunk.get('response_type', '')
            content = chunk.get('content', '')
            done = chunk.get('done', False)

            if response_type == 'answer':
                message_idx += 1
                if content:
                    pending_answer = _append_bounded(pending_answer, content)

                if done:
                    is_final = True

                if message_idx % 8 == 0 or is_final:
                    yield provider_message.MessageChunk(
                        role='assistant',
                        content=pending_answer,
                        is_final=is_final,
                    )

            elif response_type == 'error':
                raise errors.WeKnoraAPIError(f'WeKnora 服务错误: {content}')

        if chunk is None:
            raise errors.WeKnoraAPIError('WeKnora API 没有返回任何响应，请检查网络连接和API配置')

        if not is_final and pending_answer:
            yield provider_message.MessageChunk(
                role='assistant',
                content=pending_answer,
                is_final=True,
            )

    async def run(self, query: pipeline_query.Query) -> typing.AsyncGenerator[provider_message.Message, None]:
        """运行请求"""
        app_type = self.pipeline_config['ai']['weknora-api']['app-type']

        if await query.adapter.is_stream_output_supported():
            msg_idx = 0
            if app_type == 'agent':
                async for msg in self._agent_chat_messages_chunk(query):
                    msg_idx += 1
                    msg.msg_sequence = msg_idx
                    yield msg
            elif app_type == 'chat':
                async for msg in self._chat_messages_chunk(query):
                    msg_idx += 1
                    msg.msg_sequence = msg_idx
                    yield msg
            else:
                raise errors.WeKnoraAPIError(f'不支持的 WeKnora 应用类型: {app_type}')
        else:
            if app_type == 'agent':
                async for msg in self._agent_chat_messages(query):
                    yield msg
            elif app_type == 'chat':
                async for msg in self._chat_messages(query):
                    yield msg
            else:
                raise errors.WeKnoraAPIError(f'不支持的 WeKnora 应用类型: {app_type}')
