"""Http trigger for AWEL."""
import json
import logging
from enum import Enum
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    List,
    Optional,
    Type,
    Union,
    cast,
    get_origin,
)

from dbgpt._private.pydantic import BaseModel, Field

from ..dag.base import DAG
from ..flow import (
    IOField,
    OperatorCategory,
    OperatorType,
    OptionValue,
    Parameter,
    ResourceCategory,
    ResourceType,
    ViewMetadata,
    register_resource,
)
from ..operators.base import BaseOperator
from ..operators.common_operator import MapOperator
from ..util._typing_util import _parse_bool
from ..util.http_util import join_paths
from .base import Trigger

if TYPE_CHECKING:
    from fastapi import APIRouter, FastAPI
    from starlette.requests import Request

    RequestBody = Union[Type[Request], Type[BaseModel], Type[Dict[str, Any]], Type[str]]
    CommonRequestType = Union[Request, BaseModel, Dict[str, Any], str, None]
    StreamingPredictFunc = Callable[[CommonRequestType], bool]

logger = logging.getLogger(__name__)


class AWELHttpError(RuntimeError):
    """AWEL Http Error."""

    def __init__(self, msg: str, code: Optional[str] = None):
        """Init the AWELHttpError."""
        super().__init__(msg)
        self.msg = msg
        self.code = code


def _default_streaming_predict_func(body: "CommonRequestType") -> bool:
    if isinstance(body, BaseModel):
        body = body.dict()
    elif isinstance(body, str):
        try:
            body = json.loads(body)
        except Exception:
            return False
    elif not isinstance(body, dict):
        return False
    streaming = body.get("streaming") or body.get("stream")
    return _parse_bool(streaming)


class BaseHttpBody(BaseModel):
    """Http body.

    For http request body or response body.
    """

    @classmethod
    def get_body_class(cls) -> Type:
        """Get body class.

        Returns:
            Type: The body class.
        """
        return cls

    def get_body(self) -> Any:
        """Get the body."""
        return self

    @classmethod
    def streaming_predict_func(cls) -> Optional["StreamingPredictFunc"]:
        """Get the streaming predict function."""
        return _default_streaming_predict_func

    def streaming_response(self) -> bool:
        """Whether the response is streaming.

        Returns:
            bool: Whether the response is streaming.
        """
        return False


@register_resource(
    label="Dict Http Body",
    name="dict_http_body",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Parse the request body as a dict or response body as a dict",
)
class DictHttpBody(BaseHttpBody):
    """Dict http body."""

    _default_body: Optional[Dict[str, Any]] = None

    @classmethod
    def get_body_class(cls) -> Type[Dict[str, Any]]:
        """Get body class.

        Just return Dict[str, Any] here.

        Returns:
            Type[Dict[str, Any]]: The body class.
        """
        return Dict[str, Any]

    def get_body(self) -> Dict[str, Any]:
        """Get the body."""
        if self._default_body is None:
            raise AWELHttpError("DictHttpBody is not set")
        return self._default_body


@register_resource(
    label="String Http Body",
    name="string_http_body",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Parse the request body as a string or response body as string",
)
class StringHttpBody(BaseHttpBody):
    """String http body."""

    _default_body: Optional[str] = None

    @classmethod
    def get_body_class(cls) -> Type[str]:
        """Get body class.

        Just return str here.

        Returns:
            Type[str]: The body class.
        """
        return str

    def get_body(self) -> str:
        """Get the body."""
        if self._default_body is None:
            raise AWELHttpError("StringHttpBody is not set")
        return self._default_body


@register_resource(
    label="Request Http Body",
    name="request_http_body",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Parse the request body as a starlette Request",
)
class RequestHttpBody(BaseHttpBody):
    """Http trigger body."""

    _default_body: Optional["Request"] = None

    @classmethod
    def get_body_class(cls) -> Type["Request"]:
        """Get the request body type.

        Just return Request here.

        Returns:
            Type[Request]: The request body type.
        """
        from starlette.requests import Request

        return Request

    def get_body(self) -> "Request":
        """Get the body."""
        if self._default_body is None:
            raise AWELHttpError("RequestHttpBody is not set")
        return self._default_body


class CommonLLMHTTPRequestContext(BaseModel):
    """Common LLM http request context."""

    conv_uid: Optional[str] = Field(
        default=None, description="The conversation id of the model inference"
    )
    span_id: Optional[str] = Field(
        default=None, description="The span id of the model inference"
    )
    chat_mode: Optional[str] = Field(
        default="chat_awel_flow",
        description="The chat mode",
        examples=["chat_awel_flow", "chat_normal"],
    )
    user_name: Optional[str] = Field(
        default=None, description="The user name of the model inference"
    )
    sys_code: Optional[str] = Field(
        default=None, description="The system code of the model inference"
    )
    extra: Optional[Dict[str, Any]] = Field(
        default=None, description="The extra info of the model inference"
    )


@register_resource(
    label="Common LLM Http Request Body",
    name="common_llm_http_request_body",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Parse the request body as a common LLM http body",
)
class CommonLLMHttpRequestBody(BaseHttpBody):
    """Common LLM http request body."""

    model: str = Field(
        ..., description="The model name", examples=["gpt-3.5-turbo", "proxyllm"]
    )
    messages: Union[str, List[str]] = Field(
        ..., description="User input messages", examples=["Hello", "How are you?"]
    )
    stream: bool = Field(default=False, description="Whether return stream")

    temperature: Optional[float] = Field(
        default=None,
        description="What sampling temperature to use, between 0 and 2. Higher values "
        "like 0.8 will make the output more random, while lower values like 0.2 will "
        "make it more focused and deterministic.",
    )
    max_new_tokens: Optional[int] = Field(
        default=None,
        description="The maximum number of tokens that can be generated in the chat "
        "completion.",
    )
    context: CommonLLMHTTPRequestContext = Field(
        default_factory=CommonLLMHTTPRequestContext,
        description="The context of the model inference",
    )


@register_resource(
    label="Common LLM Http Response Body",
    name="common_llm_http_response_body",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Parse the response body as a common LLM http body",
)
class CommonLLMHttpResponseBody(BaseHttpBody):
    """Common LLM http response body."""

    text: str = Field(
        ..., description="The generated text", examples=["Hello", "How are you?"]
    )
    error_code: int = Field(
        default=0, description="The error code, 0 means no error", examples=[0, 1]
    )
    metrics: Optional[Dict[str, Any]] = Field(
        default=None,
        description="The metrics of the model, like the number of tokens generated",
    )


class HttpTrigger(Trigger):
    """Http trigger for AWEL.

    Http trigger is used to trigger a DAG by http request.
    """

    metadata = ViewMetadata(
        label="Http Trigger",
        name="http_trigger",
        category=OperatorCategory.TRIGGER,
        operator_type=OperatorType.INPUT,
        description="Trigger your workflow by http request",
        inputs=[],
        outputs=[],
        parameters=[
            Parameter.build_from(
                "API Endpoint", "endpoint", str, description="The API endpoint"
            ),
            Parameter.build_from(
                "Http Methods",
                "methods",
                str,
                optional=True,
                default="GET",
                description="The methods of the API endpoint",
                options=[
                    OptionValue(label="HTTP Method GET", name="http_get", value="GET"),
                    OptionValue(label="HTTP Method PUT", name="http_put", value="PUT"),
                    OptionValue(
                        label="HTTP Method POST", name="http_post", value="POST"
                    ),
                    OptionValue(
                        label="HTTP Method DELETE", name="http_delete", value="DELETE"
                    ),
                ],
            ),
            Parameter.build_from(
                "Http Request Trigger Body",
                "http_trigger_body",
                BaseHttpBody,
                optional=True,
                default=None,
                description="The request body of the API endpoint",
                resource_type=ResourceType.CLASS,
            ),
            Parameter.build_from(
                "Streaming Response",
                "streaming_response",
                bool,
                optional=True,
                default=False,
                description="Whether the response is streaming",
            ),
            Parameter.build_from(
                "Http Response Body",
                "http_response_body",
                BaseHttpBody,
                optional=True,
                default=None,
                description="The response body of the API endpoint",
                resource_type=ResourceType.CLASS,
            ),
            Parameter.build_from(
                "Response Media Type",
                "response_media_type",
                str,
                optional=True,
                default=None,
                description="The response media type",
            ),
            Parameter.build_from(
                "Http Status Code",
                "status_code",
                int,
                optional=True,
                default=200,
                description="The http status code",
            ),
        ],
    )

    def __init__(
        self,
        endpoint: str,
        methods: Optional[Union[str, List[str]]] = "GET",
        request_body: Optional["RequestBody"] = None,
        http_trigger_body: Optional[Type[BaseHttpBody]] = None,
        streaming_response: bool = False,
        streaming_predict_func: Optional["StreamingPredictFunc"] = None,
        http_response_body: Optional[Type[BaseHttpBody]] = None,
        response_model: Optional[Type] = None,
        response_headers: Optional[Dict[str, str]] = None,
        response_media_type: Optional[str] = None,
        status_code: Optional[int] = 200,
        router_tags: Optional[List[str | Enum]] = None,
        register_to_app: bool = False,
        **kwargs,
    ) -> None:
        """Initialize a HttpTrigger."""
        super().__init__(**kwargs)
        if not endpoint.startswith("/"):
            endpoint = "/" + endpoint
        if not request_body and http_trigger_body:
            request_body = http_trigger_body.get_body_class()
            streaming_predict_func = http_trigger_body.streaming_predict_func()
        if not response_model and http_response_body:
            response_model = http_response_body.get_body_class()
        self._endpoint = endpoint
        self._methods = [methods] if isinstance(methods, str) else methods
        self._req_body = request_body
        self._streaming_response = _parse_bool(streaming_response)
        self._streaming_predict_func = streaming_predict_func
        self._response_model = response_model
        self._status_code = status_code
        self._router_tags = router_tags
        self._response_headers = response_headers
        self._response_media_type = response_media_type
        self._end_node: Optional[BaseOperator] = None
        self._register_to_app = register_to_app

    async def trigger(self) -> None:
        """Trigger the DAG. Not used in HttpTrigger."""
        pass

    def register_to_app(self) -> bool:
        """Register the trigger to a FastAPI app.

        Returns:
            bool: Whether register to app, if not register to app, will register to
                router.
        """
        return self._register_to_app

    def mount_to_router(
        self, router: "APIRouter", global_prefix: Optional[str] = None
    ) -> None:
        """Mount the trigger to a router.

        Args:
            router (APIRouter): The router to mount the trigger.
            global_prefix (Optional[str], optional): The global prefix of the router.
        """
        path = (
            join_paths(global_prefix, self._endpoint)
            if global_prefix
            else self._endpoint
        )
        dynamic_route_function = self._create_route_func()
        router.api_route(
            self._endpoint,
            methods=self._methods,
            response_model=self._response_model,
            status_code=self._status_code,
            tags=self._router_tags,
        )(dynamic_route_function)

        logger.info(f"Mount http trigger success, path: {path}")

    def mount_to_app(self, app: "FastAPI", global_prefix: Optional[str] = None) -> None:
        """Mount the trigger to a FastAPI app.

        TODO: The performance of this method is not good, need to be optimized.

        Args:
            app (FastAPI): The FastAPI app.
            global_prefix (Optional[str], optional): The global prefix of the app.
                Defaults to None.
        """
        from dbgpt.util.fastapi import PriorityAPIRouter

        path = (
            join_paths(global_prefix, self._endpoint)
            if global_prefix
            else self._endpoint
        )
        dynamic_route_function = self._create_route_func()
        router = cast(PriorityAPIRouter, app.router)
        router.add_api_route(
            path,
            dynamic_route_function,
            methods=self._methods,
            response_model=self._response_model,
            status_code=self._status_code,
            tags=self._router_tags,
            priority=10,
        )
        app.openapi_schema = None
        app.middleware_stack = None
        logger.info(f"Mount http trigger success, path: {path}")

    def remove_from_app(
        self, app: "FastAPI", global_prefix: Optional[str] = None
    ) -> None:
        """Remove the trigger from a FastAPI app.

        Args:
            app (FastAPI): The FastAPI app.
            global_prefix (Optional[str], optional): The global prefix of the app.
                Defaults to None.
        """
        from fastapi import APIRouter

        path = (
            join_paths(global_prefix, self._endpoint)
            if global_prefix
            else self._endpoint
        )
        app_router = cast(APIRouter, app.router)
        for i, r in enumerate(app_router.routes):
            if r.path_format == path:  # type: ignore
                # TODO, remove with path and methods
                del app_router.routes[i]

    def _create_route_func(self):
        from inspect import Parameter, Signature
        from typing import get_type_hints

        from starlette.requests import Request

        is_query_method = (
            all(method in ["GET", "DELETE"] for method in self._methods)
            if self._methods
            else True
        )

        async def _trigger_dag_func(body: Union[Request, BaseModel, str, None]):
            streaming_response = self._streaming_response
            if self._streaming_predict_func:
                streaming_response = self._streaming_predict_func(body)
            elif isinstance(body, BaseHttpBody):
                # BaseHttpBody, read streaming flag from body
                streaming_response = _default_streaming_predict_func(body)
            dag = self.dag
            if not dag:
                raise AWELHttpError("DAG is not set")
            return await _trigger_dag(
                body,
                dag,
                streaming_response,
                self._response_headers,
                self._response_media_type,
            )

        def create_route_function(name, req_body_cls: Optional["RequestBody"]):
            async def route_function_request(request: Request):
                return await _trigger_dag_func(request)

            async def route_function_none():
                return await _trigger_dag_func(None)

            route_function_request.__name__ = name
            route_function_none.__name__ = name

            if not req_body_cls:
                return route_function_none
            if req_body_cls == Request:
                return route_function_request

            if is_query_method:
                if req_body_cls == str:
                    raise AWELHttpError(
                        f"Query methods {self._methods} not support str type"
                    )

                async def route_function_get(**kwargs):
                    if req_body_cls == dict or get_origin(req_body_cls) == dict:
                        body = kwargs
                    else:
                        body = req_body_cls(**kwargs)
                    return await _trigger_dag_func(body)

                if isinstance(req_body_cls, type) and issubclass(
                    req_body_cls, BaseModel
                ):
                    fields = req_body_cls.__fields__  # type: ignore
                    parameters = []
                    for field_name, field in fields.items():
                        default_value = (
                            Parameter.empty if field.required else field.default
                        )
                        parameters.append(
                            Parameter(
                                name=field_name,
                                kind=Parameter.KEYWORD_ONLY,
                                default=default_value,
                                annotation=field.outer_type_,
                            )
                        )
                elif req_body_cls == Dict[str, Any] or req_body_cls == dict:
                    raise AWELHttpError(
                        f"Query methods {self._methods} not support dict type"
                    )
                else:
                    parameters = []
                route_function_get.__signature__ = Signature(parameters)  # type: ignore
                if isinstance(req_body_cls, type):
                    route_function_get.__annotations__ = get_type_hints(req_body_cls)
                route_function_get.__name__ = name
                return route_function_get
            else:

                async def route_function(body: req_body_cls):  # type: ignore
                    return await _trigger_dag_func(body)

                route_function.__name__ = name
                return route_function

        function_name = f"AWEL_trigger_route_{self._endpoint.replace('/', '_')}"
        if isinstance(self._req_body, type) and (  # noqa: SIM114
            issubclass(self._req_body, Request)
            or issubclass(self._req_body, BaseModel)
            or issubclass(self._req_body, dict)
            or issubclass(self._req_body, str)
        ):  # noqa: SIM114
            request_model = self._req_body
        elif get_origin(self._req_body) == dict and not is_query_method:
            request_model = self._req_body
        elif is_query_method:
            request_model = None
        else:
            err_msg = f"Unsupported request body type {self._req_body}"
            raise AWELHttpError(err_msg)

        dynamic_route_function = create_route_function(function_name, request_model)
        logger.info(
            f"mount router function {dynamic_route_function}({function_name}), "
            f"endpoint: {self._endpoint}, methods: {self._methods}"
        )
        return dynamic_route_function


async def _trigger_dag(
    body: Any,
    dag: DAG,
    streaming_response: Optional[bool] = False,
    response_headers: Optional[Dict[str, str]] = None,
    response_media_type: Optional[str] = None,
) -> Any:
    from fastapi import BackgroundTasks
    from fastapi.responses import StreamingResponse

    leaf_nodes = dag.leaf_nodes
    if len(leaf_nodes) != 1:
        raise ValueError("HttpTrigger just support one leaf node in dag")
    end_node = cast(BaseOperator, leaf_nodes[0])
    if not streaming_response:
        return await end_node.call(call_data=body)
    else:
        headers = response_headers
        media_type = response_media_type if response_media_type else "text/event-stream"
        if not headers:
            headers = {
                "Content-Type": "text/event-stream",
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "Transfer-Encoding": "chunked",
            }
        generator = await end_node.call_stream(call_data=body)
        background_tasks = BackgroundTasks()
        background_tasks.add_task(dag._after_dag_end)
        return StreamingResponse(
            generator,
            headers=headers,
            media_type=media_type,
            background=background_tasks,
        )


_PARAMETER_ENDPOINT = Parameter.build_from(
    "API Endpoint", "endpoint", str, description="The API endpoint"
)
_PARAMETER_METHODS_POST_PUT = Parameter.build_from(
    "Http Methods",
    "methods",
    str,
    optional=True,
    default="POST",
    description="The methods of the API endpoint",
    options=[
        OptionValue(label="HTTP Method PUT", name="http_put", value="PUT"),
        OptionValue(label="HTTP Method POST", name="http_post", value="POST"),
    ],
)
_PARAMETER_METHODS_ALL = Parameter.build_from(
    "Http Methods",
    "methods",
    str,
    optional=True,
    default="GET",
    description="The methods of the API endpoint",
    options=[
        OptionValue(label="HTTP Method GET", name="http_get", value="GET"),
        OptionValue(label="HTTP Method DELETE", name="http_delete", value="DELETE"),
        OptionValue(label="HTTP Method PUT", name="http_put", value="PUT"),
        OptionValue(label="HTTP Method POST", name="http_post", value="POST"),
    ],
)
_PARAMETER_STREAMING_RESPONSE = Parameter.build_from(
    "Streaming Response",
    "streaming_response",
    bool,
    optional=True,
    default=False,
    description="Whether the response is streaming",
)
_PARAMETER_RESPONSE_BODY = Parameter.build_from(
    "Http Response Body",
    "http_response_body",
    BaseHttpBody,
    optional=True,
    default=None,
    description="The response body of the API endpoint",
    resource_type=ResourceType.CLASS,
)
_PARAMETER_MEDIA_TYPE = Parameter.build_from(
    "Response Media Type",
    "response_media_type",
    str,
    optional=True,
    default=None,
    description="The response media type",
)
_PARAMETER_STATUS_CODE = Parameter.build_from(
    "Http Status Code",
    "status_code",
    int,
    optional=True,
    default=200,
    description="The http status code",
)


class DictHttpTrigger(HttpTrigger):
    """Dict http trigger for AWEL.

    Parse the request body as a dict.
    """

    metadata = ViewMetadata(
        label="Dict Http Trigger",
        name="dict_http_trigger",
        category=OperatorCategory.TRIGGER,
        operator_type=OperatorType.INPUT,
        description="Trigger your workflow by http request, and parse the request body"
        " as a dict",
        inputs=[],
        outputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                dict,
                description="The request body of the API endpoint",
            ),
        ],
        parameters=[
            _PARAMETER_ENDPOINT.new(),
            _PARAMETER_METHODS_POST_PUT.new(),
            _PARAMETER_STREAMING_RESPONSE.new(),
            _PARAMETER_RESPONSE_BODY.new(),
            _PARAMETER_MEDIA_TYPE.new(),
            _PARAMETER_STATUS_CODE.new(),
        ],
    )

    def __init__(
        self,
        endpoint: str,
        methods: Optional[Union[str, List[str]]] = "POST",
        streaming_response: bool = False,
        http_response_body: Optional[Type[BaseHttpBody]] = None,
        response_media_type: Optional[str] = None,
        status_code: Optional[int] = 200,
        router_tags: Optional[List[str | Enum]] = None,
        **kwargs,
    ):
        """Initialize a DictHttpTrigger."""
        if not router_tags:
            router_tags = ["AWEL DictHttpTrigger"]
        super().__init__(
            endpoint,
            methods,
            streaming_response=streaming_response,
            request_body=dict,
            http_response_body=http_response_body,
            response_media_type=response_media_type,
            status_code=status_code,
            router_tags=router_tags,
            register_to_app=True,
            **kwargs,
        )


class StringHttpTrigger(HttpTrigger):
    """String http trigger for AWEL."""

    metadata = ViewMetadata(
        label="String Http Trigger",
        name="string_http_trigger",
        category=OperatorCategory.TRIGGER,
        operator_type=OperatorType.INPUT,
        description="Trigger your workflow by http request, and parse the request body"
        " as a string",
        inputs=[],
        outputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                str,
                description="The request body of the API endpoint, parse as a json "
                "string",
            ),
        ],
        parameters=[
            _PARAMETER_ENDPOINT.new(),
            _PARAMETER_METHODS_POST_PUT.new(),
            _PARAMETER_STREAMING_RESPONSE.new(),
            _PARAMETER_RESPONSE_BODY.new(),
            _PARAMETER_MEDIA_TYPE.new(),
            _PARAMETER_STATUS_CODE.new(),
        ],
    )

    def __init__(
        self,
        endpoint: str,
        methods: Optional[Union[str, List[str]]] = "POST",
        streaming_response: bool = False,
        http_response_body: Optional[Type[BaseHttpBody]] = None,
        response_media_type: Optional[str] = None,
        status_code: Optional[int] = 200,
        router_tags: Optional[List[str | Enum]] = None,
        **kwargs,
    ):
        """Initialize a StringHttpTrigger."""
        if not router_tags:
            router_tags = ["AWEL StringHttpTrigger"]
        super().__init__(
            endpoint,
            methods,
            streaming_response=streaming_response,
            request_body=str,
            http_response_body=http_response_body,
            response_media_type=response_media_type,
            status_code=status_code,
            router_tags=router_tags,
            register_to_app=True,
            **kwargs,
        )


class CommonLLMHttpTrigger(HttpTrigger):
    """Common LLM http trigger for AWEL."""

    metadata = ViewMetadata(
        label="Common LLM Http Trigger",
        name="common_llm_http_trigger",
        category=OperatorCategory.TRIGGER,
        operator_type=OperatorType.INPUT,
        description="Trigger your workflow by http request, and parse the request body "
        "as a common LLM http body",
        inputs=[],
        outputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                CommonLLMHttpRequestBody,
                description="The request body of the API endpoint, parse as a common "
                "LLM http body",
            ),
        ],
        parameters=[
            _PARAMETER_ENDPOINT.new(),
            _PARAMETER_METHODS_POST_PUT.new(),
            _PARAMETER_STREAMING_RESPONSE.new(),
            _PARAMETER_RESPONSE_BODY.new(),
            _PARAMETER_MEDIA_TYPE.new(),
            _PARAMETER_STATUS_CODE.new(),
        ],
    )

    def __init__(
        self,
        endpoint: str,
        methods: Optional[Union[str, List[str]]] = "POST",
        streaming_response: bool = False,
        http_response_body: Optional[Type[BaseHttpBody]] = None,
        response_media_type: Optional[str] = None,
        status_code: Optional[int] = 200,
        router_tags: Optional[List[str | Enum]] = None,
        **kwargs,
    ):
        """Initialize a CommonLLMHttpTrigger."""
        if not router_tags:
            router_tags = ["AWEL CommonLLMHttpTrigger"]
        super().__init__(
            endpoint,
            methods,
            streaming_response=streaming_response,
            request_body=CommonLLMHttpRequestBody,
            http_response_body=http_response_body,
            response_media_type=response_media_type,
            status_code=status_code,
            router_tags=router_tags,
            register_to_app=True,
            **kwargs,
        )


@register_resource(
    label="Example Http Response",
    name="example_http_response",
    category=ResourceCategory.HTTP_BODY,
    resource_type=ResourceType.CLASS,
    description="Example Http Request",
)
class ExampleHttpResponse(BaseHttpBody):
    """Example Http Response.

    Just for test.
    Register as a resource.
    """

    server_res: str = Field(..., description="The server response from Operator")
    request_body: Dict[str, Any] = Field(
        ..., description="The request body from Http request"
    )


class ExampleHttpHelloOperator(MapOperator[dict, ExampleHttpResponse]):
    """Example Http Hello Operator.

    Just for test.
    """

    metadata = ViewMetadata(
        label="Example Http Hello Operator",
        name="example_http_hello_operator",
        category=OperatorCategory.COMMON,
        parameters=[],
        inputs=[
            IOField.build_from(
                "Http Request Body",
                "request_body",
                dict,
                description="The request body of the API endpoint(Dict[str, Any])",
            )
        ],
        outputs=[
            IOField.build_from(
                "Response Body",
                "response_body",
                ExampleHttpResponse,
                description="The response body of the API endpoint",
            )
        ],
        description="Example Http Hello Operator",
    )

    def __int__(self, **kwargs):
        """Initialize a ExampleHttpHelloOperator."""
        super().__init__(**kwargs)

    async def map(self, request_body: dict) -> ExampleHttpResponse:
        """Map the request body to response body."""
        print(f"Receive input value: {request_body}")
        name = request_body.get("name")
        age = request_body.get("age")
        server_res = f"Hello, {name}, your age is {age}"
        return ExampleHttpResponse(server_res=server_res, request_body=request_body)


class RequestBodyToDictOperator(MapOperator[CommonLLMHttpRequestBody, Dict[str, Any]]):
    """Request body to dict operator."""

    metadata = ViewMetadata(
        label="Request Body To Dict Operator",
        name="request_body_to_dict_operator",
        category=OperatorCategory.COMMON,
        parameters=[
            Parameter.build_from(
                "Prefix Key",
                "prefix_key",
                str,
                optional=True,
                default=None,
                description="The prefix key of the dict, link 'context.extra'",
            )
        ],
        inputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                CommonLLMHttpRequestBody,
                description="The request body of the API endpoint",
            )
        ],
        outputs=[
            IOField.build_from(
                "Response Body",
                "response_body",
                dict,
                description="The response body of the API endpoint",
            )
        ],
        description="Request body to dict operator",
    )

    def __init__(self, prefix_key: Optional[str] = None, **kwargs):
        """Initialize a RequestBodyToDictOperator."""
        super().__init__(**kwargs)
        self._key = prefix_key

    async def map(self, request_body: CommonLLMHttpRequestBody) -> Dict[str, Any]:
        """Map the request body to response body."""
        dict_value = request_body.dict()
        if not self._key:
            return dict_value
        else:
            keys = self._key.split(".")
            for k in keys:
                dict_value = dict_value[k]
            if isinstance(dict_value, dict):
                raise ValueError(
                    f"Prefix key {self._key} is not a valid key of the request body"
                )
            return dict_value


class UserInputParsedOperator(MapOperator[CommonLLMHttpRequestBody, Dict[str, Any]]):
    """User input parsed operator."""

    metadata = ViewMetadata(
        label="User Input Parsed Operator",
        name="user_input_parsed_operator",
        category=OperatorCategory.COMMON,
        parameters=[
            Parameter.build_from(
                "Key",
                "key",
                str,
                optional=True,
                default="user_input",
                description="The key of the dict, link 'user_input'",
            )
        ],
        inputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                CommonLLMHttpRequestBody,
                description="The request body of the API endpoint",
            )
        ],
        outputs=[
            IOField.build_from(
                "User Input Dict",
                "user_input_dict",
                dict,
                description="The user input dict of the API endpoint",
            )
        ],
        description="User input parsed operator",
    )

    def __init__(self, key: str = "user_input", **kwargs):
        """Initialize a UserInputParsedOperator."""
        self._key = key
        super().__init__(**kwargs)

    async def map(self, request_body: CommonLLMHttpRequestBody) -> Dict[str, Any]:
        """Map the request body to response body."""
        return {self._key: request_body.messages}


class RequestedParsedOperator(MapOperator[CommonLLMHttpRequestBody, str]):
    """User input parsed operator."""

    metadata = ViewMetadata(
        label="Request Body Parsed To String Operator",
        name="request_body_to_str__parsed_operator",
        category=OperatorCategory.COMMON,
        parameters=[
            Parameter.build_from(
                "Key",
                "key",
                str,
                optional=True,
                default="messages",
                description="The key of the dict, link 'user_input'",
            )
        ],
        inputs=[
            IOField.build_from(
                "Request Body",
                "request_body",
                CommonLLMHttpRequestBody,
                description="The request body of the API endpoint",
            )
        ],
        outputs=[
            IOField.build_from(
                "User Input String",
                "user_input_str",
                str,
                description="The user input dict of the API endpoint",
            )
        ],
        description="User input parsed operator",
    )

    def __init__(self, key: str = "user_input", **kwargs):
        """Initialize a UserInputParsedOperator."""
        self._key = key
        super().__init__(**kwargs)

    async def map(self, request_body: CommonLLMHttpRequestBody) -> str:
        """Map the request body to response body."""
        dict_value = request_body.dict()
        if not self._key or self._key not in dict_value:
            raise ValueError(
                f"Prefix key {self._key} is not a valid key of the request body"
            )
        return dict_value[self._key]
