"""
The layer that interfaces with the CleverThis service that this Adapter is integrating with.
Methods here will invoke the actual CleverThis Service code, which are passed as Callable object.
"""

import asyncio
import enum
import inspect
import json
import pathlib
import re
from asyncio import AbstractEventLoop
from collections import defaultdict
from typing import Any, Callable, Coroutine, Dict, List

from aio_pika.abc import (
    AbstractExchange,
    AbstractRobustChannel,
    AbstractRobustConnection,
)
from aiohttp import ClientResponse, ClientSession
from fastapi import HTTPException, UploadFile
from fastapi.params import File
from fastapi.routing import APIRoute
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
from starlette.datastructures import Headers
from starlette.responses import FileResponse

from amqp.adapter import serializer
from amqp.adapter.data_parser import AMQDataParser
from amqp.adapter.data_response_factory import DataResponseFactory
from amqp.adapter.file_handler import FileHandler, StreamingFileHandler
from amqp.adapter.logging_utils import (
    logging_debug,
    logging_error,
    logging_info,
    logging_warning,
)
from amqp.adapter.pydantic_serializer import python_type_to_json
from amqp.adapter.type_descriptor import TypeDescriptor
from amqp.config.amq_configuration import AMQConfiguration
from amqp.model.model import AMQErrorMessage, AMQMessage, DataMessage, DataResponse
from amqp.router.router_base import AMQ_REPLY_TO_EXCHANGE
from amqp.router.utils import await_result

g_extra_init_data: dict = dict()


async def _create_reply_exchange_and_queue(
    connection: AbstractRobustConnection,
) -> tuple[AbstractRobustChannel, AbstractExchange, str]:
    """
    Create objects for reply.
    Return (channel for reply queue, exchange for reply, reply queue name)
    """

    # Wrap calls to RabbitMQ API to make it Awaitable, so it can be run thread-safe with
    # correct event loop. This is needed because the RabbitMQ API is not thread-safe and needs
    # to be called in the context of event loop the first RabbitMQ connection was created with.
    _channel = await connection.channel()
    _exchange_name: str = AMQ_REPLY_TO_EXCHANGE
    _exchange = await _channel.get_exchange(name=_exchange_name, ensure=True)
    _queue = await _channel.declare_queue(exclusive=False)  # Declare a unique, non-exclusive queue
    _queue_name = _queue.name
    await _queue.bind(
        exchange=_exchange_name,
        routing_key=_queue_name,  # Use queue name as routing key
    )
    logging_debug(f"Publishing to exchange: {_exchange_name}, q: {_queue_name}")
    return _channel, _exchange, _queue_name


async def _convert_file_response(
    obj: FileResponse,
    connection: AbstractRobustConnection,
    file_handler: FileHandler,
    loop: AbstractEventLoop | None,
) -> str:
    """
    Converts a FileResponse object to a JSON string containing the filename and stream name.
    The file content itself is transmitted through the named stream (which is implemented as
    temporary dedicated queue.
    """
    # All calls to RabbitMQ API have to be called threadsafe, because those appear to be attached
    # to parent event loop, and need to execute at that event loop, not the current one
    # allow coroutine to execute. calling _future.result() will block the entire thread
    # and the coroutine will never execute
    _channel, _exchange, _routing_key = await await_result(
        asyncio.run_coroutine_threadsafe(_create_reply_exchange_and_queue(connection), loop)
    )
    # publish_file also needs RabbitMQ API, ensure the loop is passed as argument
    (_stream_name, _file_size) = await file_handler.publish_file(
        _exchange, obj.path, _routing_key, loop
    )
    # as above, but luckily this time we don't need to wait for the result
    asyncio.run_coroutine_threadsafe(_channel.close(), loop)
    return json.dumps(
        {
            "files": [
                {
                    "filename": obj.filename,
                    "mediaType": "application/octet-stream",
                    "streamName": _stream_name,
                    "fileSize": _file_size,
                }
            ]
        }
    )


def is_likely_json(text: str) -> bool:
    """Quick test to see if should convert the response string to JSON format."""
    if not isinstance(text, str):
        return False
    _text = text.strip()
    return (_text.startswith("{") and _text.endswith("}")) or (
        _text.startswith("[") and _text.endswith("]")
    )


# =============  CleverThisServiceAdapter =============
class CleverThisServiceAdapter:
    """
    A base class for CleverThis service adapter that handles the incoming AMQP messages and routes
    them to the appropriate CleverThisService API endpoint.
    IMPORTANT: This class is not intended to be used directly. It should be subclassed for
    each CleverThis service and override the methods to handle the specific API calls.
    """

    def __init__(
        self,
        amq_configuration: AMQConfiguration,
        routes: List[APIRoute],
        session: ClientSession = None,
        authenticated_user_arg_name: str = "authenticated_user",
        extra_init_data=None,
    ):
        """
        Initializes the CleverThisServiceAdapter with the given AMQ configuration and optional HTTP
        session. The HTTP session is for the case when the adapter uses loose coupling with
        the CleverThis service via HTTP REST API.
        """
        if extra_init_data is None:
            extra_init_data = dict()
        self.authenticated_user_arg_name = authenticated_user_arg_name
        self.amq_configuration = amq_configuration
        self.session = session
        self.service_host = self.amq_configuration.dispatch.service_host
        self.service_port = self.amq_configuration.dispatch.service_port
        self.loop: AbstractEventLoop | None = None
        self.require_authenticated_user = (
            self.amq_configuration.amq_adapter.require_authenticated_user
        )
        self.routes = self.group_routes_by_method(api_routes=routes)
        global g_extra_init_data
        g_extra_init_data = extra_init_data or {}

    def get_endpoint(self, endpoint: Any) -> Any:
        """
        Optionally, Service can override this method to alter the endpoint mapping.
        This is useful for example when the CleverThis service has a JSON wrapper
        around the endpoint, like CleverSwarm, which might be the preferred endpoint to call.

        :param endpoint: The original endpoint to be called.
        :return: The actual endpoint to be called.
        """
        return endpoint

    def clone_and_adapt_route(self, original_route: APIRoute) -> APIRoute:
        """
        Creates a clone of APIRoute. Checks if there's preferred endpoint with json serialization.
        It also adjusts the path regex to ensure it matches the path in different API versions.
        """

        # Need to adapt also the path regex to ensure it matches the path in different API versions
        # Ensures the leading '^' is followed by any match and there's no '/' before final '$'
        _new_regex_string = "^.*" + original_route.path_regex.pattern.lstrip("^").rstrip("/$") + "$"
        _new_route: APIRoute = APIRoute(
            path=original_route.path,
            endpoint=self.get_endpoint(
                original_route.endpoint
            ),  # Service can alter the endpoint mapping here
            response_model=original_route.response_model,
            status_code=original_route.status_code,
            tags=original_route.tags.copy() if original_route.tags else None,
            dependencies=(
                original_route.dependencies.copy() if original_route.dependencies else None
            ),
            summary=original_route.summary,
            description=original_route.description,
            response_description=original_route.response_description,
            responses=(original_route.responses.copy() if original_route.responses else None),
            deprecated=original_route.deprecated,
            name=original_route.name,
            methods=original_route.methods.copy() if original_route.methods else None,
            operation_id=original_route.operation_id,
            response_model_include=original_route.response_model_include,
            response_model_exclude=original_route.response_model_exclude,
            response_model_by_alias=original_route.response_model_by_alias,
            response_model_exclude_unset=original_route.response_model_exclude_unset,
            response_model_exclude_defaults=original_route.response_model_exclude_defaults,
            response_model_exclude_none=original_route.response_model_exclude_none,
            include_in_schema=original_route.include_in_schema,
            response_class=original_route.response_class,
            dependency_overrides_provider=original_route.dependency_overrides_provider,
            callbacks=(original_route.callbacks.copy() if original_route.callbacks else None),
            openapi_extra=(
                original_route.openapi_extra.copy() if original_route.openapi_extra else None
            ),
            generate_unique_id_function=original_route.generate_unique_id_function,
        )
        _new_route.path_regex = re.compile(_new_regex_string)
        return _new_route

    def group_routes_by_method(self, api_routes: List[APIRoute]) -> Dict[str, List[APIRoute]]:
        """
        Groups API routes by their HTTP method. This is important to ensure that URLPath is matched
        correctly, as different methods may share the same URLPath (e.g. GET, POST PUT, DELETE).
        """
        _method_to_routes = defaultdict(list)  # Automatically initializes new lists for new keys
        for _route in api_routes:
            if (
                isinstance(_route, APIRoute) and _route.methods
            ):  # Ensure methods exist (should always be true for valid routes)
                for method in _route.methods:
                    _method_to_routes[method].append(self.clone_and_adapt_route(_route))

        return dict(_method_to_routes)

    def get_content_type(self, message_headers: Dict[str, List[str]]) -> str:
        """
        Pull the value of the Content-Type header from the message headers.
        :param message_headers: The headers of the message.
        return: The content type of the message.
        """
        for header, values in message_headers.items():
            if header.lower() == "content-type":
                return values[0].split(";")[0]
        return "application/json"

    async def get_current_user(self, token: str) -> object:
        """
        To Be Overriden in subclass for given CleverThis Service, to return the current user based
        on the token, in format appropriate for the service.

        :param token: The token to be used for authentication.

        :return: A user object or None if the user is not authenticated.
        """
        return None

    async def on_message(self, message: AMQMessage) -> DataResponse:
        """
        Called when a message is received from the AMQP.
        :param message:
        :return: AMQP response class with operation status code and optional error details.
        """
        _auth_user: Any | None = None
        _amq_response: Any | None = None

        logging_debug(f"on_message: require_authenticated_user={self.require_authenticated_user}")
        if self.require_authenticated_user:
            _auth: str = message.headers().get("Authorization", "")
            logging_debug(f"Auth: {_auth}")
            if isinstance(_auth, List):
                _auth = _auth[0]
            if "Bearer" in _auth:
                try:
                    token = _auth.split(" ")[1]
                    logging_info(f"Token: {token}")
                    _auth_user = await self.get_current_user(token)
                except Exception as error:
                    logging_error(f"on_message: Error getting user: {error}")

        if _auth_user or not self.require_authenticated_user or message.path().endswith("/login"):
            method = message.method().lower()
            if method == "get":
                _amq_response = await self.get(message, _auth_user)
            elif method == "put":
                _amq_response = await self.put(message, _auth_user)
            elif method == "post":
                _amq_response = await self.post(message, _auth_user)
            elif method == "head":
                _amq_response = await self.head(message, _auth_user)
            elif method == "delete":
                _amq_response = await self.delete(message, _auth_user)
            else:
                raise ValueError(f"Unexpected HTTP method: {message.method()}")
        else:
            _amq_response = DataResponseFactory.of_error_message(
                message.id(),
                AMQErrorMessage(401, "Unauthorized", "Unauthorized"),
                message.trace_info(),
            )
        logging_info(
            f"ALL DONE in on_message id:{message.id()}, resp code:{_amq_response.response_code()}"
        )
        return _amq_response

    async def _authorize_forward_request(
        self, message: DataMessage, request_coroutine, auth_user: Any
    ) -> DataResponse:
        """
        This method is used to authorize the request and forward it to the CleverThis Service API.
        Used only for loose / 3rd party mode of coupling with the Service
        """
        # AMQDataParser.parse_request_body(message)
        # TODO: implement authorization logic here - ensure the security related headers are set
        # This depends on what kind of security is required by the CleverThis service.
        # However at this time there is no loosely coupled Service.

        return await request_coroutine

    # ==============================================================================================
    # =====================  C l e v e r T h i s    A P I    m e t h o d s  ========================
    # ==============================================================================================

    async def get(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the GET requests for the CleverThis API. In tight coupling (self.session is None),
        use path, path params and query string to determine the endpoint method to invoke.
        In loose coupling (self.session is not None), forward the request as REST request using
        the session, i.e. attempt to call the REST endpoint directly using HTTP protocol.

        :param message: message from the AMQP that contains the GET request
        :param auth_user: user authenticated by the API Gateway, as UserSchem

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """

        if self.session is None:
            return await self.handle_no_body_payload(message, auth_user, "GET")

        # Forwards as REST - try to invoke the service directly on this path
        url = f"http://{self.service_host}:{self.service_port}{message.path()}"
        headers = {**message.headers(), **message.trace_info()}
        logging_info(
            f"SEND HEADERS out: {', '.join(f'{k}={v}' for k, v in headers.items())}, REQ={url}"
        )
        _http_resp: ClientResponse = await self.session.get(url, headers=headers)
        return DataResponseFactory.of(
            message.id(),
            _http_resp.status,
            _http_resp.content_type,
            await _http_resp.read(),
            message.trace_info(),
        )

    async def put(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the PUT requests for the CleverThis API. Uses path, path params, query string and
        body payload to determine the endpoint method to invoke and its input arguments
        In loose coupling (self.session is not None), attempt to call the REST endpoint directly
        using HTTP protocol.

        :param message: message from the AMQP that contains the PUT request
        :param auth_user: user authenticated by the API Gateway, as UserSchem

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_body_payload(message, auth_user, "PUT")

        # Forwards as REST - try to invoke the service directly on this path
        return await self._authorize_forward_request(
            message,
            self.session.put(
                f"http://{self.service_host}:{self.service_port}{message.path()}",
                data=message.body(),
                headers={**message.headers(), **message.trace_info()},
            ),
            auth_user,
        )

    async def post(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the POST requests for the CleverThis API. Uses path, path params, query string and
        body payload to determine the endpoint method to invoke and its input arguments
        In loose coupling (self.session is not None), attempt to call the REST endpoint directly
        using HTTP protocol.

        :param message: message from AMQP that contains the POST request, including body and headers
        :param auth_user: user authenticated by the API Gateway, as UserSchem

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_body_payload(message, auth_user, "POST")

        return await self._authorize_forward_request(
            message,
            self.session.post(
                f"http://{self.service_host}:{self.service_port}{message.path()}",
                data=message.body(),
                headers={**message.headers(), **message.trace_info()},
            ),
            auth_user,
        )

    async def head(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the HEAD requests for the CleverThis API. In tight coupling (self.session is None),
        use path, path params and query string to determine the endpoint method to invoke.
        In loose coupling (self.session is not None), HEAD call is not supported.

        :param message: message from AMQP containing the HEAD request, including body and headers
        :param auth_user: user authenticated by the API Gateway, as UserSchema

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_no_body_payload(message, auth_user, "HEAD")

        raise NotImplementedError

    async def delete(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the DELETE requests for the CleverThis API.

        :param message: message from AMQP that contains the DELETE request, including body & headers
        :param auth_user: user authenticated by the API Gateway, as UserSchema

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_no_body_payload(message, auth_user, "DELETE")

        return await self._authorize_forward_request(
            message,
            self.session.post(
                f"http://{self.service_host}:{self.service_port}{message.path()}",
                data=message.body(),
                headers={**message.headers(), **message.trace_info()},
            ),
            auth_user,
        )

    async def options(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the OPTIONS requests. In tight coupling (self.session is None),
        use path, path params and query string to determine the endpoint method to invoke.
        In loose coupling (self.session is not None), forward the request as REST request using
        the session, i.e. attempt to call the REST endpoint directly using HTTP protocol.

        :param message: message from AMQP that contains the OPTIONS request, including headers
        :param auth_user: user authenticated by the API Gateway, as UserSchema

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_no_body_payload(message, auth_user, "OPTIONS")

        raise NotImplementedError

    async def patch(self, message: AMQMessage, auth_user: Any) -> DataResponse:
        """
        Handles the PATCH requests for the CleverBRAG API.

        :param message: message from AMQP that contains the PATCH request, including body & headers
        :param auth_user: user authenticated by the API Gateway, as UserSchema

        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        if self.session is None:
            return await self.handle_no_body_payload(message, auth_user, "PATCH")

        return await self._authorize_forward_request(
            message,
            self.session.post(
                f"http://{self.service_host}:{self.service_port}{message.path()}",
                data=message.body(),
                headers={**message.headers(), **message.trace_info()},
            ),
            auth_user,
        )

    # ==============================================================================================
    # =================  C l e v e r S w a r m   p a y l o a d   h a n d l e r s  ==================
    # ==============================================================================================

    async def handle_no_body_payload(
        self, message: AMQMessage, auth_user: Any, method: str
    ) -> DataResponse:
        """
        Handles the requests that do not have a body payload.
        :param message: message from the AMQP that contains the request
        :param auth_user: user authenticated by the API Gateway, as UserSchem
        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        _routes = self.routes.get(method, [])
        _path, _args = AMQDataParser.parse_get_url(message.path())
        for _route in _routes:
            # TODO: here the regex will match with path prefix,
            #  but it might not be a stable feature that we could rely on.
            #  Need to figure out something stable for the prefix path.
            _match = _route.path_regex.match(_path)
            if _match:
                _args.update(_match.groupdict())
                _signature_args = inspect.signature(_route.endpoint).parameters
                if self.authenticated_user_arg_name in _signature_args.keys():
                    _args[self.authenticated_user_arg_name] = auth_user
                # If the path matches, call the corresponding function
                return await self.call_cleverthis_api(
                    message,
                    _args,
                    api_method=_route.endpoint,
                    success_code=_route.status_code,
                )

        return DataResponseFactory.of(
            message.id(),
            404,
            "text/plain",
            str(f"Destination location [{message.path()}] does not exists").encode("utf-8"),
            message.trace_info(),
        )

    async def handle_body_payload(
        self, message: AMQMessage, auth_user: Any, method: str
    ) -> DataResponse:
        """
        Handles the requests that have a body payload, including Multipart data.
        :param message: message from the AMQP that contains the request
        :param auth_user: user authenticated by the API Gateway, as UserSchem
        :param method: HTTP method (POST, PUT, etc.)
        :return: DataResponse to be sent back to the API Gateway via AMQP
        """
        _form_data = AMQDataParser.parse_request_body(message)
        _routes = self.routes.get(method, [])
        _path, _query_args = AMQDataParser.parse_get_url(message.path())
        for _route in _routes:
            _match = _route.path_regex.match(_path)
            if _match:
                # If the path matches, call the corresponding function
                _form_data.update(_match.groupdict())
                _form_data.update(_query_args)
                for _body_param in _route.dependant.body_params:
                    if _body_param.name in _form_data:
                        _file_data = _form_data.get(_body_param.name, None)
                        actual_filepath = message.extra_data.get(_body_param.name, None)
                        _form_data[_body_param.name] = (
                            (
                                _body_param.type_.__name__ == "UploadFile"
                                or isinstance(_body_param.field_info, File)
                            )
                            and _file_data is not None
                            and actual_filepath is not None
                            and UploadFile(
                                file=pathlib.Path(actual_filepath).open("rb"),
                                size=_file_data.get("size"),
                                filename=_file_data.get("filename"),
                                headers=Headers({"Content-Type": _file_data.get("mediaType")}),
                            )
                            or _form_data[_body_param.name]
                        )

                _verified_args = {}
                _required_args = getattr(_route.endpoint, "__annotations__", {})
                _signature_args = inspect.signature(_route.endpoint).parameters
                for arg in _required_args:
                    if arg in _form_data:
                        _verified_args[arg] = _form_data[arg]
                    else:
                        # needed for CleverSwarm /login  input
                        if _required_args[arg].__name__ == "OAuth2PasswordRequestForm":
                            try:
                                _verified_args[arg] = OAuth2PasswordRequestForm(**_form_data)
                            except Exception as e:
                                print(f"Error: {e}")
                if self.authenticated_user_arg_name in _signature_args.keys():
                    _verified_args[self.authenticated_user_arg_name] = auth_user

                return await self.call_cleverthis_api(
                    message,
                    _verified_args,
                    api_method=_route.endpoint,
                    success_code=_route.status_code if _route.status_code else 200,
                )

        return DataResponseFactory.of(
            message.id(),
            404,
            "text/plain",
            str(f"Destination location [{message.path()}] does not exists").encode("utf-8"),
            message.trace_info(),
        )

    def _verify_and_instantiate_arguments(
        self,
        args: Any,
        api_method: Callable[[Any, Any], Coroutine],
    ) -> dict:
        """
        Check the provided arguments against the API method signature and convert the incoming string value
        into correct expected input object.
        The logic used FastAPI annotation and would use the value from 'default_factory' invocation (where defined)
        or 'default' value, as defined in the FastAPI annotation, to create default value in case the object is not in
        the input values provided by the caller.

        """
        _verified_args = {}
        try:
            _sig = inspect.signature(api_method)
            _required_args = list(_sig.parameters.values())
            # _required_args = getattr(api_method, '__annotations__', {})
            for arg in _required_args:
                if arg.name in args:
                    _annotation = arg.annotation
                    _str_annotation = str(_annotation)
                    # convert to another type if arg is not a str
                    if arg.name == self.authenticated_user_arg_name:
                        _verified_args[arg.name] = args.get(arg.name)
                    elif _annotation == int:
                        _verified_args[arg.name] = int(args.get(arg.name))
                    elif _annotation == float:
                        _verified_args[arg.name] = float(args.get(arg.name))
                    elif _annotation == bool:
                        _verified_args[arg.name] = serializer.str_to_bool(args.get(arg.name))
                    elif (
                        "Optional" in _str_annotation
                        or _str_annotation.lower().startswith("list")
                        or _str_annotation.startswith("typing.List")
                        or "class" in _str_annotation
                    ):
                        _type_descr = TypeDescriptor(
                            _str_annotation, extra_init_data=g_extra_init_data
                        )
                        try:
                            _payload = args.get(arg.name, "")
                            _verified_args[arg.name] = _type_descr.instantiate(
                                _payload, is_json=is_likely_json(_payload)
                            )
                        except Exception as err:
                            logging_error(
                                f"Could not instantiate argument {arg}, defaulting to its string value",
                                err,
                            )
                            _verified_args[arg.name] = args.get(arg.name, None)
                    else:
                        _temp = args.get(arg.name)
                        if isinstance(arg.annotation, enum.EnumType) or isinstance(
                            arg.annotation, enum.Enum
                        ):
                            try:
                                _temp = arg.annotation[_temp]
                            except KeyError as ke:
                                # some enums have string value different to key spelling, try different thing
                                try:
                                    _temp = arg.annotation(_temp)
                                except ValueError as ve:
                                    logging_warning(
                                        f"cannot convert '{_temp}' to Enum type {arg.annotation}, {ke}, {ve}"
                                    )
                            print(f"ENUM -> {_temp}")
                        _verified_args[arg.name] = _temp
                else:
                    if hasattr(arg, "default"):
                        _temp = arg.default
                        if hasattr(_temp, "default_factory") and _temp.default_factory is not None:
                            _temp = _temp.default_factory()
                        elif hasattr(_temp, "default"):
                            _temp = _temp.default
                        if not str(_temp) == "<class 'inspect._empty'>":
                            _verified_args[arg.name] = _temp
                    else:
                        logging_warning(
                            f"{api_method.__name__}: Missing required argument: {arg.name}"
                        )
        except Exception as e:
            logging_error("Failed to validate and instantiate input args", e)

        return _verified_args

    async def call_cleverthis_api(
        self,
        message: AMQMessage,
        args: Any,
        api_method: Callable[[Any, Any], Coroutine],
        success_code: int,
    ) -> DataResponse:
        """
        Handles POST and PUT requests, which typically contain possibly complex payload data,
        passed as a dictionary. Invokes the provided API method with the given arguments and
        authenticated user, and returns the response as DataResponse.

        :param message: DataMessage containing the details of the call and the payload
        :param args: map of the arguments to pass to the API method
        :param api_method: Callable - the method to invoke. Last parameter is the authenticated user
        :param success_code: HTTP code to be returned when operation suceeds

        :return: DataResponse class
        """
        try:
            _verified_args = self._verify_and_instantiate_arguments(
                args=args, api_method=api_method
            )
            logging_info(
                "INVOKE ENDPOINT: [%s], ARGS: {%s}",
                api_method.__name__,
                ", ".join(f"{k}={v}" for k, v in _verified_args.items()),
            )
            _result: Any = await api_method(**_verified_args)
            logging_info(f"ENDPOINT RESULT: [{api_method.__name__}], RESULT: {str(_result)}")

            if isinstance(_result, FileResponse):
                _json_result, _content_type = (
                    await _convert_file_response(
                        _result,
                        message.connection,
                        file_handler=StreamingFileHandler(),
                        loop=self.loop,
                    ),
                    "application/octet-stream",
                )
            elif isinstance(_result, BaseModel):
                _json_result = _result.json()
                _content_type = "application/json"
            else:
                if isinstance(_result, str):
                    _json_result = _result
                else:
                    _json_result = python_type_to_json(_result)
                _content_type = "application/json"

            _binary_result = (
                _json_result.encode("utf-8")
                if hasattr(_json_result, "encode")
                else (
                    _json_result.body
                    if hasattr(_json_result, "body")
                    else (_json_result.read() if hasattr(_json_result, "read") else _json_result)
                )
            )
            logging_info(
                f"ALL DONE in call_cleverthis_api id:{message.id()}, resp code:{success_code}"
            )
            return DataResponseFactory.of(
                message.id(),
                success_code,
                _content_type,
                _binary_result,
                message.trace_info(),
            )
        except HTTPException as http_error:
            logging_error(f"Service REST endpoint invocation failed: {http_error}")
            _content = str(http_error.detail)
            if not is_likely_json(_content):
                return DataResponseFactory.of_error_message(
                    message.id(),
                    AMQErrorMessage(
                        http_error.status_code,
                        "Service Invocation Failed",
                        _content,
                    ),
                    message.trace_info(),
                )
            return DataResponseFactory.of(
                message.id(),
                http_error.status_code,
                "application/json",
                _content.encode("utf-8"),
                message.trace_info(),
            )
        except Exception as error:
            logging_error(f"Service endpoint invocation failed: {error}")
            _content = str(error)
            if not is_likely_json(_content):
                return DataResponseFactory.of_error_message(
                    message.id(),
                    AMQErrorMessage(
                        500,
                        "Service Invocation Failed",
                        _content,
                    ),
                    message.trace_info(),
                )
            return DataResponseFactory.of(
                message.id(),
                500,
                "application/json",
                _content.encode("utf-8"),
                message.trace_info(),
            )
