# Werkzeug
from werkzeug.wrappers import Request

# Utils
from retic.services.json import parse


class Body(object):
    """Class for the body from a request"""

    def __init__(self, type, value):
        """Define the type of the object"""
        self.type: dict = type
        """Value of the object"""
        self.value: dict = value

    @property
    def type(self):
        return self.__type

    @type.setter
    def type(self, value):
        self.__type = value

    @property
    def value(self):
        return self.__value

    @value.setter
    def value(self, value):
        self.__value = value


class Request(Request):
    """Initial instance of the Request Class"""

    @property
    def retic(self):
        return self.__retic

    @retic.setter
    def retic(self, value):
        if hasattr(self, "retic"):
            raise TypeError(
                "error: You can't assign the settings of this ways. if you want to assign from an object please use to *req.retic.clear()* function"
            )
        self.__retic = value

    def config(self):
        """Set another attributes to Instance"""
        self.body: Body = self._get_body()
        self.retic = self.params = {}
        return self

    def param(self, key: str, default_value=None):
        """Returns the value of the parameter with the specified name.

        :param key: Name of the variable to set
        :param default_value: Value of the variable if this one doesn't exist
        """
        if key in self.params:
            return self.params.get(key)
        elif key in self.body.value:
            return self.body.value.get(key)
        elif key in self.args:
            return self.args.get(key)
        return self.retic.get(key, default_value)

    def set(self, key: str, value: any = None):
        """Set a value in the requests (req).

        Please note that names are not case sensitive.

        :param key: Name of the variable to set
        :param value: Value of the variable
        """
        try:
            return self.retic.setdefault(key.lower(), value)
        except KeyError:
            return None

    def get(self, key: str):
        """Returns the value of the request (req).

        Please note that names are not case sensitive.

        :param key: Name of the variable to find
        """
        try:
            return self.retic.get(key.lower(), None)
        except KeyError:
            return None

    def all_params(self):
        """Returns the value of all the parameters sent in the request,
        combined into a single dictionary.

        It includes parameters parsed from the URL path, the request body,
        and the query string, in that order."""
        return {**self.params, **self.body.value, **self.args, **self.retic}

    def _get_body(self):
        """Get the body for a request from to client. If this one is not exists, return

        ``{ type: "undefiend", value: "undefiend" }``

        The response takes possibly four keys: json, form, text, and raw. 

        For example if the request body is a json object, you will get:

        ``{ type: "json", value: json_object }``

        Also, if a exception is detected, you will get

        ``{ type: "error", value: error_object }``
        """
        _type = ''
        _value = ''
        _content_type = self.headers.get('content-type', type=str)
        try:
            if not _content_type:
                _type = 'undefiend'
                _value = 'undefiend'
            elif _content_type.startswith('application/json'):
                _type = "json"
                _value = parse(self.get_data())
            elif _content_type.startswith('multipart/form-data') \
                    or _content_type.startswith('application/x-www-form-urlencoded'):
                _type = 'form'
                _value = self.form
            elif _content_type.startswith('text/'):
                _type = "text"
                _value = self.get_data().decode("utf-8")
            else:
                _type = 'raw'
                _value = self.get_data()
            return Body(
                type=_type,
                value=_value,
            )
        except Exception as e:
            return Body(
                type='error',
                value=e,
            )
