from functools import wraps
import inspect
from typing import Any, Callable, Union

from requests.adapters import Response

from mstrio import config
from mstrio.api.exceptions import MstrException, PartialSuccess, Success
from mstrio.utils.helper import get_default_args_from_func, response_handler


def get_args_and_bind_values(func: Callable[[Any], Any], *args, **kwargs):
    signature = inspect.signature(func)
    return signature.bind(*args, **kwargs).arguments


class ErrorHandler:
    """An easy-to-use class decorator designed to replace
    logic responsible for displaying the error message in API wrappers.

    Attributes:
        err_msg(str): error message to be displayed in case of error

    Usage:
        to replace the code below

          if not response.ok:
            if error_msg is None:
              error_msg = f'Error deleting Datasource Login with ID {id}'
            response_handler(response, error_msg)
          return response

        use the decorator in a following way

           @ErrorHandler(err_msg='Error deleting Datasource Login with ID {id}')
           def func(connection, id):
              ...

        the strings in curly braces will be replaced
        with the appropriate values if they appear in the function arguments
    """

    def __init__(self, err_msg: str):
        self._err_msg = err_msg

    def __call__(self, func: Callable[[Any], Any]):

        @wraps(func)
        def inner(*args, **kwargs):
            response = func(*args, **kwargs)
            error_msg = kwargs.get("error_msg") if kwargs.get("error_msg") else self._err_msg
            if not response.ok:
                handler_kwargs = self._get_resp_handler_kwargs(kwargs)
                error_msg = self._replace_with_values(error_msg, func, *args, **kwargs)
                response_handler(response, error_msg, **handler_kwargs)
            return response

        return inner

    @staticmethod
    def _replace_with_values(err_msg: str, func: Callable[[Any], Any], *args, **kwargs):
        all_args = get_args_and_bind_values(func, *args, **kwargs)
        for arg in all_args:
            arg_name, arg_value = arg, all_args[arg]
            err_msg = err_msg.replace(f'{{{arg_name}}}', str(arg_value))
        return err_msg

    @staticmethod
    def _get_resp_handler_kwargs(decorated_func_kwargs):
        default_args = get_default_args_from_func(response_handler)
        for arg in default_args:
            default_args[arg] = decorated_func_kwargs.get(arg, default_args[arg])
        return default_args


def bulk_operation_response_handler(
        response: Response,
        unpack_value: str = None) -> Union[PartialSuccess, Success, MstrException]:
    """Handle partial success and other statuses from bulk operation."""
    response_body = response.json()
    if response.ok and unpack_value:
        response_body = response_body[unpack_value]

    if response.status_code == 200:
        err = Success(response_body)
    elif response.status_code == 207:
        err = PartialSuccess(response_body)
    else:
        err = MstrException(response_body)

    if config.verbose:
        print(err)
    return err
