import json

from .constants import TYPES
from .headers import HTTPHeaderDict
from .helpers import trigger_methods


class Response:
    """
    Response is used to declare and compose an HTTP mock responses fields.

    It provides a chainable DSL interface for easier and declarative usage.

    Arguments:
        status (int): HTTP response status code. Defaults to ``200``.
        headers (dict): HTTP response headers.
        body (str|bytes): HTTP response body.
        json (str|dict|list): HTTP response JSON body.
        xml (str): HTTP response XML body.
        type (str): HTTP response content MIME type.
        file (str): file path to HTTP body response.
    """

    _KEY_ORDER = (
        "body",
        "content",
        "file",
        "header",
        "headers",
        "json",
        "mock",
        "set",
        "status",
        "type",
        "xml",
    )

    def __init__(self, **kw):
        self._status = 200
        self._mock = None
        self._body = None
        self._headers = HTTPHeaderDict()

        # Trigger response method based on input arguments
        trigger_methods(self, kw, self._KEY_ORDER)

    def status(self, code=200):
        """
        Defines the response status code.

        Arguments:
            code (int): response status code.

        Returns:
            self: ``pook.Response`` current instance.
        """
        self._status = int(code)
        return self

    def header(self, key, value):
        """
        Defines a new response header.
        Alias to ``Response.header()``.

        Arguments:
            header (str): header name.
            value (str): header value.

        Returns:
            self: ``pook.Response`` current instance.
        """
        if type(key) is tuple:
            key, value = str(key[0]), key[1]

        headers = {key: value}
        self._headers.extend(headers)
        return self

    def headers(self, headers):
        """
        Defines a new response header.
        Alias to ``Response.header()``.

        Arguments:
            header (str): header name.
            value (str): header value.

        Returns:
            self: ``pook.Response`` current instance.
        """
        self._headers.extend(headers)
        return self

    def set(self, header, value):
        """
        Defines a new response header.
        Alias to ``Response.header()``.

        Arguments:
            header (str): header name.
            value (str): header value.

        Returns:
            self: ``pook.Response`` current instance.
        """
        self._headers[header] = value
        return self

    def type(self, name):
        """
        Defines the response ``Content-Type`` header.
        Alias to ``Response.content(mime)``.

        You can pass one of the following type aliases instead of the full
        MIME type representation:

        - ``json`` = ``application/json``
        - ``xml`` = ``application/xml``
        - ``html`` = ``text/html``
        - ``text`` = ``text/plain``
        - ``urlencoded`` = ``application/x-www-form-urlencoded``
        - ``form`` = ``application/x-www-form-urlencoded``
        - ``form-data`` = ``application/x-www-form-urlencoded``

        Arguments:
            value (str): type alias or header value to match.

        Returns:
            self: ``pook.Response`` current instance.
        """
        return self.content(name)

    def content(self, name):
        """
        Defines the response ``Content-Type`` header.

        You can pass one of the following type aliases instead of the full
        MIME type representation:

        - ``json`` = ``application/json``
        - ``xml`` = ``application/xml``
        - ``html`` = ``text/html``
        - ``text`` = ``text/plain``
        - ``urlencoded`` = ``application/x-www-form-urlencoded``
        - ``form`` = ``application/x-www-form-urlencoded``
        - ``form-data`` = ``application/x-www-form-urlencoded``

        Arguments:
            value (str): type alias or header value to match.

        Returns:
            self: ``pook.Response`` current instance.
        """
        self._headers["Content-Type"] = TYPES.get(name, name)
        return self

    def body(self, body, *, chunked=False):
        """
        Defines response body data.

        Arguments:
            body (str|bytes|list): response body to use.
            chunked (bool): return a chunked response.

        Returns:
            self: ``pook.Response`` current instance.
        """
        if hasattr(body, "encode"):
            body = body.encode("utf-8", "backslashreplace")
        elif isinstance(body, list):
            for i, chunk in enumerate(body):
                if hasattr(chunk, "encode"):
                    body[i] = chunk.encode("utf-8", "backslashreplace")

        self._body = body

        if chunked:
            self.header("Transfer-Encoding", "chunked")
        return self

    def json(self, data):
        """
        Defines the mock response JSON body.

        Arguments:
            data (dict|list|str): JSON body data.

        Returns:
            self: ``pook.Response`` current instance.
        """
        self._headers["Content-Type"] = "application/json"
        if not isinstance(data, str):
            data = json.dumps(data, indent=4)

        return self.body(data)

    def xml(self, xml):
        """
        Defines the mock response XML body.

        For not it only supports ``str`` as input type.

        Arguments:
            xml (str): XML body data to use.

        Returns:
            self: ``pook.Response`` current instance.
        """
        return self.body(xml)

    def file(self, path):
        """
        Defines the response body from file contents.

        Arguments:
            path (str): disk file path to load.

        Returns:
            self: ``pook.Response`` current instance.
        """
        with open(path, "rb") as f:
            return self.body(f.read())

    @property
    def mock(self):
        """
        Reference to mock instance.
        """
        return self._mock

    @mock.setter
    def mock(self, mock):
        self._mock = mock

    def __repr__(self):
        """
        Returns an human friendly readable instance data representation.

        Returns:
            str
        """
        args = []
        for key in ("headers", "status", "body"):
            value = getattr(self, f"_{key}")
            args.append(f"{key}={value}")
        return "Response(\n    {}\n)".format(",\n    ".join(args))
