from __future__ import annotations

import codecs
import typing
import json
import httpx
import uuid
import traceback

from .. import runner
from ...core import app
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
import langbot_plugin.api.entities.builtin.provider.message as provider_message

_MAX_LANGFLOW_LINE_CHARS = 1024 * 1024
_MAX_LANGFLOW_TOTAL_BYTES = 16 * 1024 * 1024
_MAX_LANGFLOW_RESPONSE_BYTES = 1024 * 1024


async def _iter_limited_lines(
    response: httpx.Response,
) -> typing.AsyncGenerator[str, None]:
    decoder = codecs.getincrementaldecoder('utf-8')('replace')
    buffer = ''
    total_bytes = 0
    async for chunk in response.aiter_bytes(chunk_size=8192):
        total_bytes += len(chunk)
        if total_bytes > _MAX_LANGFLOW_TOTAL_BYTES:
            raise ValueError('Langflow stream exceeds the runtime limit')
        buffer += decoder.decode(chunk)
        while '\n' in buffer:
            line, buffer = buffer.split('\n', 1)
            if len(line) > _MAX_LANGFLOW_LINE_CHARS:
                raise ValueError('Langflow event exceeds the runtime limit')
            yield line.rstrip('\r')
        if len(buffer) > _MAX_LANGFLOW_LINE_CHARS:
            raise ValueError('Langflow event exceeds the runtime limit')
    buffer += decoder.decode(b'', final=True)
    if buffer:
        if len(buffer) > _MAX_LANGFLOW_LINE_CHARS:
            raise ValueError('Langflow event exceeds the runtime limit')
        yield buffer.rstrip('\r')


async def _read_limited_response(response: httpx.Response) -> bytes:
    body = bytearray()
    async for chunk in response.aiter_bytes(chunk_size=8192):
        body.extend(chunk)
        if len(body) > _MAX_LANGFLOW_RESPONSE_BYTES:
            raise ValueError('Langflow response exceeds the runtime limit')
    return bytes(body)


@runner.runner_class('langflow-api')
class LangflowAPIRunner(runner.RequestRunner):
    """Langflow API 对话请求器"""

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

    async def _build_request_payload(self, query: pipeline_query.Query) -> dict:
        """构建请求负载

        Args:
            query: 用户查询对象

        Returns:
            dict: 请求负载
        """
        # 获取用户消息文本
        user_message_text = ''
        if isinstance(query.user_message.content, str):
            user_message_text = query.user_message.content
        elif isinstance(query.user_message.content, list):
            for item in query.user_message.content:
                if item.type == 'text':
                    user_message_text += item.text

        # 从配置中获取 input_type 和 output_type，如果未配置则使用默认值
        input_type = self.pipeline_config['ai']['langflow-api'].get('input_type', 'chat')
        output_type = self.pipeline_config['ai']['langflow-api'].get('output_type', 'chat')

        # 构建基本负载
        payload = {
            'output_type': output_type,
            'input_type': input_type,
            'input_value': user_message_text,
            'session_id': str(uuid.uuid4()),
        }

        # 如果配置中有tweaks，则添加到负载中
        tweaks = json.loads(self.pipeline_config['ai']['langflow-api'].get('tweaks'))
        if tweaks:
            payload['tweaks'] = tweaks

        return payload

    async def run(
        self, query: pipeline_query.Query
    ) -> typing.AsyncGenerator[provider_message.Message | provider_message.MessageChunk, None]:
        """运行请求

        Args:
            query: 用户查询对象

        Yields:
            Message: 回复消息
        """
        # 检查是否支持流式输出
        is_stream = False
        try:
            is_stream = await query.adapter.is_stream_output_supported()
        except AttributeError:
            is_stream = False

        # 从配置中获取API参数
        base_url = self.pipeline_config['ai']['langflow-api']['base-url']
        api_key = self.pipeline_config['ai']['langflow-api']['api-key']
        flow_id = self.pipeline_config['ai']['langflow-api']['flow-id']

        # 构建API URL
        url = f'{base_url.rstrip("/")}/api/v1/run/{flow_id}'

        # 构建请求负载
        payload = await self._build_request_payload(query)

        # 设置请求头
        headers = {'Content-Type': 'application/json', 'x-api-key': api_key}

        # 发送请求
        async with httpx.AsyncClient() as client:
            if is_stream:
                # 流式请求
                async with client.stream('POST', url, json=payload, headers=headers, timeout=120.0) as response:
                    response.raise_for_status()

                    accumulated_content = ''
                    message_count = 0

                    async for line in _iter_limited_lines(response):
                        data_str = line

                        if data_str.startswith('data: '):
                            data_str = data_str[6:]  # 移除 "data: " 前缀

                        try:
                            data = json.loads(data_str)

                            # 提取消息内容
                            message_text = ''
                            if 'outputs' in data and len(data['outputs']) > 0:
                                output = data['outputs'][0]
                                if 'outputs' in output and len(output['outputs']) > 0:
                                    inner_output = output['outputs'][0]
                                    if 'outputs' in inner_output and 'message' in inner_output['outputs']:
                                        message_data = inner_output['outputs']['message']
                                        if 'message' in message_data:
                                            message_text = message_data['message']

                            # 如果没有找到消息，尝试其他可能的路径
                            if not message_text and 'messages' in data:
                                messages = data['messages']
                                if messages and len(messages) > 0:
                                    message_text = messages[0].get('message', '')

                            if message_text:
                                # 更新累积内容
                                accumulated_content = message_text
                                message_count += 1

                                # 每8条消息或有新内容时生成一个chunk
                                if message_count % 8 == 0 or len(message_text) > 0:
                                    yield provider_message.MessageChunk(
                                        role='assistant', content=accumulated_content, is_final=False
                                    )
                        except json.JSONDecodeError:
                            # 如果不是JSON，跳过这一行
                            traceback.print_exc()
                            continue

                    # 发送最终消息
                    yield provider_message.MessageChunk(role='assistant', content=accumulated_content, is_final=True)
            else:
                # 非流式请求
                async with client.stream(
                    'POST',
                    url,
                    json=payload,
                    headers=headers,
                    timeout=120.0,
                ) as response:
                    response.raise_for_status()
                    response_data = json.loads(await _read_limited_response(response))

                # 提取消息内容
                # 根据Langflow API文档，响应结构可能在outputs[0].outputs[0].outputs.message.message中
                message_text = ''
                if 'outputs' in response_data and len(response_data['outputs']) > 0:
                    output = response_data['outputs'][0]
                    if 'outputs' in output and len(output['outputs']) > 0:
                        inner_output = output['outputs'][0]
                        if 'outputs' in inner_output and 'message' in inner_output['outputs']:
                            message_data = inner_output['outputs']['message']
                            if 'message' in message_data:
                                message_text = message_data['message']

                # 如果没有找到消息，尝试其他可能的路径
                if not message_text and 'messages' in response_data:
                    messages = response_data['messages']
                    if messages and len(messages) > 0:
                        message_text = messages[0].get('message', '')

                # 如果仍然没有找到消息，返回完整响应的字符串表示
                if not message_text:
                    message_text = json.dumps(response_data, ensure_ascii=False, indent=2)

                # 生成回复消息
                if is_stream:
                    yield provider_message.MessageChunk(role='assistant', content=message_text, is_final=True)
                else:
                    reply_message = provider_message.Message(role='assistant', content=message_text)
                    yield reply_message
