import json
from .decorators import fluent
from .headers import HTTPHeaderDict
from .helpers import trigger_methods
from .constants import TYPES

class Response(object):
    """
    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.

    Attributes:
        mock (pook.Mock): reference to mock instance.
    """

    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)

    @fluent
    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)

    @fluent
    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)

    @fluent
    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)

    @fluent
    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

    @fluent
    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.
        """
        self.content(name)

    @fluent
    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)

    @fluent
    def body(self, body):
        """
        Defines response body data.

        Arguments:
            body (str|bytes): response body to use.

        Returns:
            self: ``pook.Response`` current instance.
        """
        if isinstance(body, bytes):
            body = body.decode('utf-8')

        self._body = body

    @fluent
    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)
        self._body = data

    @fluent
    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.
        """
        self.body(xml)

    @fluent
    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, 'r') as f:
            self.body = str(f.read())

    @property
    def mock(self):
        """
        Getter accessor for `mock` attribute.
        """
        return self._mock

    @mock.setter
    def mock(self, mock):
        """
        Setter for ``mock`` attribute.
        """
        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, '_{}'.format(key))
            args.append('{}={}'.format(key, value))
        return 'Response(\n    {}\n)'.format(',\n    '.join(args))


import sys
from collections import Mapping, MutableMapping

PY3 = sys.version_info >= (3, 0)

class HTTPHeaderDict(MutableMapping):
    """
    :param headers:
        An iterable of field-value pairs. Must not contain multiple field names
        when compared case-insensitively.
    :param kwargs:
        Additional field-value pairs to pass in to ``dict.update``.

    A ``dict`` like container for storing HTTP Headers.
    Field names are stored and compared case-insensitively in compliance with
    RFC 7230. Iteration provides the first case-sensitive key seen for each
    case-insensitive pair.
    Using ``__setitem__`` syntax overwrites fields that compare equal
    case-insensitively in order to maintain ``dict``'s api. For fields that
    compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add``
    in a loop.
    If multiple fields that are equal case-insensitively are passed to the
    constructor or ``.update``, the behavior is undefined and some will be
    lost.

    Usage::

        headers = HTTPHeaderDict()
        headers.add('Set-Cookie', 'foo=bar')
        headers.add('set-cookie', 'baz=quxx')
        headers['content-length'] = '7'
        headers['SET-cookie']
        > 'foo=bar, baz=quxx'
        headers['Content-Length']
        > '7'
    """

    def __init__(self, headers=None, **kwargs):
        super(HTTPHeaderDict, self).__init__()
        self._container = {}
        if headers is not None:
            if isinstance(headers, HTTPHeaderDict):
                self._copy_from(headers)
            else:
                self.extend(headers)
        if kwargs:
            self.extend(kwargs)

    def __setitem__(self, key, val):
        self._container[key.lower()] = (key, val)
        return self._container[key.lower()]

    def __getitem__(self, key):
        val = self._container[key.lower()]
        return ', '.join(val[1:])

    def __delitem__(self, key):
        del self._container[key.lower()]

    def __contains__(self, key):
        return key.lower() in self._container

    def __eq__(self, other):
        if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
            return False
        if not isinstance(other, type(self)):
            other = type(self)(other)
        return (dict((k.lower(), v) for k, v in self.itermerged()) ==
                dict((k.lower(), v) for k, v in other.itermerged()))

    def __ne__(self, other):
        return not self.__eq__(other)

    if not PY3:  # Python 2
        iterkeys = MutableMapping.iterkeys
        itervalues = MutableMapping.itervalues

    __marker = object()

    def __len__(self):
        return len(self._container)

    def __iter__(self):
        # Only provide the originally cased names
        for vals in self._container.values():
            yield vals[0]

    def pop(self, key, default=__marker):
        """
        D.pop(k[,d]) -> v, remove specified key and return the
        corresponding value.
        If key is not found, d is returned if given, otherwise KeyError
        is raised.
        """
        # Using the MutableMapping function directly fails due to the
        # private marker. Using ordinary dict.pop would expose the
        # internal structures. So let's reinvent the wheel.
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

    def discard(self, key):
        try:
            del self[key]
        except KeyError:
            pass

    def add(self, key, val):
        """
        Adds a (name, value) pair, doesn't overwrite the value if it already
        exists.

        Usage::

            headers = HTTPHeaderDict(foo='bar')
            headers.add('Foo', 'baz')
            headers['foo']
            > 'bar, baz'
        """
        key_lower = key.lower()
        new_vals = key, val
        # Keep the common case aka no item present as fast as possible
        vals = self._container.setdefault(key_lower, new_vals)
        if new_vals is not vals:
            # new_vals was not inserted, as there was a previous one
            if isinstance(vals, list):
                # If already several items got inserted, we have a list
                vals.append(val)
            else:
                # vals should be a tuple then, i.e. only one item so far
                # Need to convert the tuple to list for further extension
                self._container[key_lower] = [vals[0], vals[1], val]

    def set(self, key, val):
        """
        Sets a header field with the given value, removing
        previous values.

        Usage::

            headers = HTTPHeaderDict(foo='bar')
            headers.set('Foo', 'baz')
            headers['foo']
            > 'baz'
        """
        key_lower = key.lower()
        new_vals = key, val
        # Keep the common case aka no item present as fast as possible
        vals = self._container.setdefault(key_lower, new_vals)
        if new_vals is not vals:
            self._container[key_lower] = [vals[0], vals[1], val]

    def extend(self, *args, **kwargs):
        """
        Generic import function for any type of header-like object.
        Adapted version of MutableMapping.update in order to insert items
        with self.add instead of self.__setitem__
        """
        if len(args) > 1:
            raise TypeError("extend() takes at most 1 positional "
                            "arguments ({0} given)".format(len(args)))
        other = args[0] if len(args) >= 1 else ()

        if isinstance(other, HTTPHeaderDict):
            for key, val in other.iteritems():
                self.add(key, val)
        elif isinstance(other, Mapping):
            for key in other:
                self.add(key, other[key])
        elif hasattr(other, "keys"):
            for key in other.keys():
                self.add(key, other[key])
        else:
            for key, value in other:
                self.add(key, value)

        for key, value in kwargs.items():
            self.add(key, value)

    def getlist(self, key):
        """
        Returns a list of all the values for the named field.
        Returns an empty list if the key doesn't exist.
        """
        try:
            vals = self._container[key.lower()]
        except KeyError:
            return []
        else:
            if isinstance(vals, tuple):
                return [vals[1]]
            else:
                return vals[1:]

    # Backwards compatibility for httplib
    getheaders = getlist
    getallmatchingheaders = getlist
    iget = getlist

    def __repr__(self):
        return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))

    def _copy_from(self, other):
        for key in other:
            val = other.getlist(key)
            if isinstance(val, list):
                # Don't need to convert tuples
                val = list(val)
            self._container[key.lower()] = [key] + val

    def copy(self):
        clone = type(self)()
        clone._copy_from(self)
        return clone

    def iteritems(self):
        """
        Iterate over all header lines, including duplicate ones.
        """
        for key in self:
            vals = self._container[key.lower()]
            for val in vals[1:]:
                yield vals[0], val

    def itermerged(self):
        """
        Iterate over all headers, merging duplicate ones together.
        """
        for key in self:
            val = self._container[key.lower()]
            yield val[0], ', '.join(val[1:])

    def items(self):
        return list(self.iteritems())

    def to_dict(self):
        return {key: values for key, values in self.items()}

    @classmethod
    def from_httplib(cls, message):  # Python 2
        """
        Read headers from a Python 2 httplib message object.
        """
        # python2.7 does not expose a proper API for exporting multiheaders
        # efficiently. This function re-reads raw lines from the message
        # object and extracts the multiheaders properly.
        headers = []

        for line in message.headers:
            if line.startswith((' ', '\t')):
                key, value = headers[-1]
                headers[-1] = (key, value + '\r\n' + line.rstrip())
                continue

            key, value = line.split(':', 1)
            headers.append((key, value.strip()))

        return cls(headers)


import re
import functools
from furl import furl
from inspect import isfunction, ismethod

from .decorators import fluent
from .response import Response
from .constants import TYPES
from .request import Request
from .matcher import MatcherEngine
from .helpers import trigger_methods
from .exceptions import PookExpiredMock
from .matchers import init as matcher

def _append_funcs(target, items):
    """
    Helper function to append functions into a given list.

    Arguments:
        target (list): receptor list to append functions.
        items (iterable): iterable that yields elements to append.
    """
    [target.append(item) for item in items
     if isfunction(item) or ismethod(item)]

def _trigger_request(instance, request):
    """
    Triggers request mock definition methods dynamically based on input
    keyword arguments passed to `pook.Mock` constructor.

    This is used to provide a more Pythonic interface vs chainable API
    approach.
    """
    if not isinstance(request, Request):
        raise TypeError('request must be instance of pook.Request')

    # Register request matchers
    for key in request.keys:
        if hasattr(instance, key):
            getattr(instance, key)(getattr(request, key))

class Mock(object):
    """
    Mock is used to declare and compose the HTTP request/response mock
    definition and matching expectations, which provides fluent API DSL.

    Arguments:
        url (str): URL to match.
            E.g: ``server.com/api?foo=bar``.
        method (str): HTTP method name to match.
            E.g: ``GET``.
        path (str): URL path to match.
            E.g: ``/api/users``.
        headers (dict): Header values to match.
            E.g: ``{'server': 'nginx'}``.
        header_present (str): Matches is a header is present.
        headers_present (list|tuple): Matches if multiple headers are present.
        type (str): Matches MIME ``Content-Type`` header.
            E.g: ``json``, ``xml``, ``html``, ``text/plain``
        content (str): Same as ``type`` argument.
        params (dict): Matches the given URL params.
        param_exists (str): Matches if a given URL param exists.
        params_exists (list|tuple): Matches if a given URL params exists.
        body (str|regex): Matches the payload body by regex or
            strict comparison.
        json (dict|list|str|regex): Matches the payload body against the given
            JSON or regular expression.
        jsonschema (dict|str): Matches the payload body against the given
            JSONSchema.
        xml (str|regex): matches the payload body against the given XML string
            or regular expression.
        file (str): Disk file path to load body from. Analog to ``body`` param.
        times (int): Mock TTL or maximum number of times that the mock can be
            matched.
        persist (bool): Enable persistent mode. Mock won't be flushed even if
            it matched one or multiple times.
        delay (int): Optional network delay simulation (only applicable when
            using ``aiohttp`` HTTP client).
        callback (function): optional callback function called every time the
            mock is matched.
        reply (int): Mock response status. Defaults to ``200``.
        response_status (int): Mock response status. Alias to ``reply`` param.
        response_headers (dict): Response headers to use.
        response_type (str): Response MIME type expression or alias.
            Analog to ``type`` param. E.g: ``json``, ``xml``, ``text/plain``.
        response_body (str): Response body to use.
        response_json (dict|list|str): Response JSON to use. If Python is
            passed, it will be serialized as JSON transparently.
        response_xml (str): XML body string to use.
        request (pook.Request): Optional. Request mock definition object.
        response (pook.Response): Optional. Response mock definition
            object.

    Returns:
        pook.Mock
    """

    def __init__(self, request=None, response=None, **kw):
        # Stores the number of times the mock should live
        self._times = 1
        # Stores the number of times the mock has been matched
        self._matches = 0
        # Stores the simulated error exception
        self._error = None
        # Stores the optional network delay in milliseconds
        self._delay = 0
        # Stores the mock persistance mode. `True` means it will live forever
        self._persist = False
        # Optional binded engine where the mock belongs to
        self._engine = None
        # Store request-response mock matched calls
        self._calls = []
        # Stores the input request instance
        self._request = request or Request()
        # Stores the response mock instance
        self._response = response or Response()
        # Stores the mock matcher engine used for outgoing traffic matching
        self.matchers = MatcherEngine()
        # Stores filters used to filter outgoing HTTP requests.
        self.filters = []
        # Stores HTTP request mappers used by the mock.
        self.mappers = []
        # Stores callback functions that will be triggered if the mock
        # matches outgoing traffic.
        self.callbacks = []

        # Triggers instance methods based on argument names
        trigger_methods(self, kw)

        # Trigger matchers based on predefined request object, if needed
        if request:
            _trigger_request(self, request)

    @fluent
    def url(self, url):
        """
        Defines the mock URL to match.
        It can be a full URL with path and query params.

        Protocol schema is optional, defaults to ``http://``.

        Arguments:
            url (str): mock URL to match. E.g: ``server.com/api``.

        Returns:
            self: current Mock instance.
        """
        self._request.url = url
        self.add_matcher(matcher('URLMatcher', url))

    @fluent
    def method(self, method):
        """
        Defines the HTTP method to match.
        Use ``*`` to match any method.

        Arguments:
            method (str): method value to match. E.g: ``GET``.

        Returns:
            self: current Mock instance.
        """
        self._request.method = method
        self.add_matcher(matcher('MethodMatcher', method))

    @fluent
    def path(self, path):
        """
        Defines a URL path to match.

        Only call this method if the URL has no path already defined.

        Arguments:
            path (str): URL path value to match. E.g: ``/api/users``.

        Returns:
            self: current Mock instance.
        """
        url = furl(self._request.rawurl)
        url.path = path
        self._request.url = url.url
        self.add_matcher(matcher('PathMatcher', path))

    @fluent
    def header(self, name, value):
        """
        Defines a URL path to match.

        Only call this method if the URL has no path already defined.

        Arguments:
            path (str): URL path value to match. E.g: ``/api/users``.

        Returns:
            self: current Mock instance.
        """
        headers = {name: value}
        self._request.headers = headers
        self.add_matcher(matcher('HeadersMatcher', headers))

    @fluent
    def headers(self, headers=None, **kw):
        """
        Defines a dictionary of arguments.

        Header keys are case insensitive.

        Arguments:
            headers (dict): headers to match.
            **headers (dict): headers to match as variadic keyword arguments.

        Returns:
            self: current Mock instance.
        """
        headers = kw if kw else headers
        self._request.headers = headers
        self.add_matcher(matcher('HeadersMatcher', headers))

    @fluent
    def header_present(self, *names):
        """
        Defines a new header matcher expectation that must be present in the
        outgoing request in order to be satisfied, no matter what value it
        hosts.

        Header keys are case insensitive.

        Arguments:
            *names (str): header or headers names to match.

        Returns:
            self: current Mock instance.

        Example::

            (pook.get('server.com/api')
                .header_present('content-type'))
        """
        for name in names:
            headers = {name: re.compile('(.*)')}
            self.add_matcher(matcher('HeadersMatcher', headers))

    @fluent
    def headers_present(self, headers):
        """
        Defines a list of headers that must be present in the
        outgoing request in order to satisfy the matcher, no matter what value
        the headers hosts.

        Header keys are case insensitive.

        Arguments:
            headers (list|tuple): header keys to match.

        Returns:
            self: current Mock instance.

        Example::

            (pook.get('server.com/api')
                .headers_present(['content-type', 'Authorization']))
        """
        headers = {name: re.compile('(.*)') for name in headers}
        self.add_matcher(matcher('HeadersMatcher', headers))

    @fluent
    def type(self, value):
        """
        Defines the request ``Content-Type`` header to match.

        You can pass one of the following 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: current Mock instance.
        """
        self.content(value)

    @fluent
    def content(self, value):
        """
        Defines the ``Content-Type`` outgoing header value to match.

        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: current Mock instance.
        """
        header = {'Content-Type': TYPES.get(value, value)}
        self._request.headers = header
        self.add_matcher(matcher('HeadersMatcher', header))

    @fluent
    def param(self, name, value):
        """
        Defines an URL param key and value to match.

        Arguments:
            name (str): param name value to match.
            value (str): param name value to match.

        Returns:
            self: current Mock instance.
        """
        self.params({name: value})

    @fluent
    def param_exists(self, name):
        """
        Checks if a given URL param name is present in the URL.

        Arguments:
            name (str): param name to check existence.

        Returns:
            self: current Mock instance.
        """
        self.params({name: re.compile('(.*)')})

    @fluent
    def params(self, params):
        """
        Defines a set of URL query params to match.

        Arguments:
            params (dict): set of params to match.

        Returns:
            self: current Mock instance.
        """
        url = furl(self._request.rawurl)
        url = url.add(params)
        self._request.url = url.url
        self.add_matcher(matcher('QueryMatcher', params))

    @fluent
    def body(self, body):
        """
        Defines the body data to match.

        ``body`` argument can be a ``str``, ``binary`` or a regular expression.

        Arguments:
            body (str|binary|regex): body data to match.

        Returns:
            self: current Mock instance.
        """
        self._request.body = body
        self.add_matcher(matcher('BodyMatcher', body))

    @fluent
    def json(self, json):
        """
        Defines the JSON body to match.

        ``json`` argument can be an JSON string, a JSON serializable
        Python structure, such as a ``dict`` or ``list`` or it can be
        a regular expression used to match the body.

        Arguments:
            json (str|dict|list|regex): body JSON to match.

        Returns:
            self: current Mock instance.
        """
        self._request.json = json
        self.add_matcher(matcher('JSONMatcher', json))

    @fluent
    def jsonschema(self, schema):
        """
        Defines a JSONSchema representation to be used for body matching.

        Arguments:
            schema (str|dict): dict or JSONSchema string to use.

        Returns:
            self: current Mock instance.
        """
        self.add_matcher(matcher('JSONSchemaMatcher', schema))

    @fluent
    def xml(self, xml):
        """
        Defines a XML body value to match.

        Arguments:
            xml (str|regex): body XML to match.

        Returns:
            self: current Mock instance.
        """
        self._request.xml = xml
        self.add_matcher(matcher('XMLMatcher', xml))

    @fluent
    def file(self, path):
        """
        Reads the body to match from a disk file.

        Arguments:
            path (str): relative or absolute path to file to read from.

        Returns:
            self: current Mock instance.
        """
        with open(path, 'r') as f:
            self.body(str(f.read()))

    @fluent
    def add_matcher(self, matcher):
        """
        Adds one or multiple custom matchers instances.

        Matchers must implement the following interface:

        - ``.__init__(expectation)``
        - ``.match(request)``
        - ``.name = str``

        Matchers can optionally inherit from ``pook.matchers.BaseMatcher``.

        Arguments:
            *matchers (pook.matchers.BaseMatcher): matchers to add.

        Returns:
            self: current Mock instance.
        """
        self.matchers.add(matcher)

    @fluent
    def use(self, *matchers):
        """
        Adds one or multiple custom matchers instances.

        Matchers must implement the following interface:

        - ``.__init__(expectation)``
        - ``.match(request)``
        - ``.name = str``

        Matchers can optionally inherit from ``pook.matchers.BaseMatcher``.

        Arguments:
            *matchers (pook.matchers.BaseMatcher): matchers to add.

        Returns:
            self: current Mock instance.
        """
        [self.add_matcher(matcher) for matcher in matchers]

    @fluent
    def times(self, times=1):
        """
        Defines the TTL limit for the current mock.

        The TTL number will determine the maximum number of times that the
        current mock can be matched and therefore consumed.

        Arguments:
            times (int): TTL number. Defaults to ``1``.

        Returns:
            self: current Mock instance.
        """
        self._times = times

    @fluent
    def persist(self, status=None):
        """
        Enables persistent mode for the current mock.

        Returns:
            self: current Mock instance.
        """
        self._persist = status if type(status) is bool else True

    @fluent
    def filter(self, *filters):
        """
        Registers one o multiple request filters used during the matching
        phase.

        Arguments:
            *mappers (function): variadic mapper functions.

        Returns:
            self: current Mock instance.
        """
        _append_funcs(self.filters, filters)

    @fluent
    def map(self, *mappers):
        """
        Registers one o multiple request mappers used during the mapping
        phase.

        Arguments:
            *mappers (function): variadic mapper functions.

        Returns:
            self: current Mock instance.
        """
        _append_funcs(self.mappers, mappers)

    @fluent
    def callback(self, *callbacks):
        """
        Registers one or multiple callback that will be called every time the
        current mock matches an outgoing HTTP request.

        Arguments:
            *callbacks (function): callback functions to call.

        Returns:
            self: current Mock instance.
        """
        _append_funcs(self.callbacks, callbacks)

    @fluent
    def delay(self, delay=1000):
        """
        Delay network response with certain milliseconds.
        Only supported by asynchronous HTTP clients, such as ``aiohttp``.

        Arguments:
            delay (int): milliseconds to delay response.

        Returns:
            self: current Mock instance.
        """
        self._delay = int(delay)

    @fluent
    def error(self, error):
        """
        Defines a simulated exception error that will be raised.

        Arguments:
            error (str|Exception): error to raise.

        Returns:
            self: current Mock instance.
        """
        self._error = RuntimeError(error) if isinstance(error, str) else error

    def reply(self, status=200, new_response=False, **kw):
        """
        Defines the mock response.

        Arguments:
            status (int, optional): response status code. Defaults to ``200``.
            **kw (dict): optional keyword arguments passed to ``pook.Response``
                constructor.

        Returns:
            pook.Response: mock response definition instance.
        """
        # Use or create a Response mock instance
        res = Response(**kw) if new_response else self._response
        # Define HTTP mandatory response status
        res.status(status or res._status)
        # Expose current mock instance in response for self-reference
        res.mock = self
        # Define mock response
        self._response = res
        # Return response
        return res

    def status(self, code=200):
        """
        Defines the response status code.
        Equivalent to ``self.reply(code)``.

        Arguments:
            code (int): response status code. Defaults to ``200``.

        Returns:
            pook.Response: mock response definition instance.
        """
        return self.reply(status=code)

    def response(self, status=200, **kw):
        """
        Defines the mock response. Alias to ``.reply()``

        Arguments:
            status (int): response status code. Defaults to ``200``.
            **kw (dict): optional keyword arguments passed to ``pook.Response``
                constructor.

        Returns:
            pook.Response: mock response definition instance.
        """
        return self.reply(status=status, **kw)

    def isdone(self):
        """
        Returns ``True`` if the mock has been matched by outgoing HTTP traffic.

        Returns:
            bool: ``True`` if the mock was matched succesfully.
        """
        return (self._persist and self._matches > 0) or self._times <= 0

    def ismatched(self):
        """
        Returns ``True`` if the mock has been matched at least once time.

        Returns:
            bool
        """
        return self._matches > 0

    @property
    def done(self):
        """
        Attribute accessor that would be ``True`` if the current mock
        is done, and therefore have been matched multiple times.

        Returns:
            bool
        """
        return self.isdone()

    @property
    def matched(self):
        """
        Accessor property that would be ``True`` if the current mock
        have been matched at least once.

        See ``Mock.total_matches`` for more information.

        Returns:
            bool
        """
        return self._matches > 0

    @property
    def total_matches(self):
        """
        Accessor property to retrieve the total number of times that the
        current mock has been matched.

        Returns:
            int
        """
        return self._matches

    @property
    def matches(self):
        """
        Accessor to retrieve the mock match calls registry.

        Returns:
            list[MockCall]
        """
        return self._calls

    @property
    def calls(self):
        """
        Accessor to retrieve the amount of mock matched calls.

        Returns:
            int
        """
        return len(self.matches)

    def match(self, request):
        """
        Matches an outgoing HTTP request against the current mock matchers.

        This method acts like a delegator to `pook.MatcherEngine`.

        Arguments:
            request (pook.Request): request instance to match.

        Raises:
            Exception: if the mock has an exception defined.

        Returns:
            tuple(bool, list[Exception]): ``True`` if the mock matches
                the outgoing HTTP request, otherwise ``False``. Also returns
                an optional list of error exceptions.
        """
        # If mock already expired, fail it
        if self._times <= 0:
            raise PookExpiredMock('Mock expired')

        # Trigger mock filters
        for test in self.filters:
            if not test(request, self):
                return False, []

        # Trigger mock mappers
        for mapper in self.mappers:
            request = mapper(request, self)
            if not request:
                raise ValueError('map function must return a request object')

        # Match incoming request against registered mock matchers
        matches, errors = self.matchers.match(request)

        # If not matched, return False
        if not matches:
            return False, errors

        # Register matched request for further inspecion and reference
        self._calls.append(request)

        # Increase mock call counter
        self._matches += 1
        if not self._persist:
            self._times -= 1

        # Raise simulated error
        if self._error:
            raise self._error

        # Trigger callback when matched
        for callback in self.callbacks:
            callback(request, self)

        return True, []

    def __call__(self, fn):
        """
        Overload Mock instance as callable object in order to be used
        as decorator definition syntax.

        Arguments:
            fn (function): function to decorate.

        Returns:
            function or pook.Mock
        """
        # Support chain sequences of mock definitions
        if isinstance(fn, Response):
            return fn.mock
        if isinstance(fn, Mock):
            return fn

        # Force type assertion and raise an error if it is not a function
        if not isfunction(fn) and not ismethod(fn):
            raise TypeError('first argument must be a method or function')

        # Remove mock to prevent decorator definition scope collision
        self._engine.remove_mock(self)

        @functools.wraps(fn)
        def decorator(*args, **kw):
            # Re-register mock on decorator call
            self._engine.add_mock(self)

            # Force engine activation, if available
            # This prevents state issue while declaring mocks as decorators.
            # This might be removed in the future.
            engine_active = self._engine.active
            if not engine_active:
                self._engine.activate()

            # Call decorated target function
            try:
                return fn(*args, **kw)
            finally:
                # Finally remove mock after function execution
                # to prevent shared state
                self._engine.remove_mock(self)

                # If the engine was not previously active, disable it
                if not engine_active:
                    self._engine.disable()

        return decorator

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

        Returns:
            str
        """
        keys = ('matches', 'times', 'persist', 'matchers', 'response')

        args = []
        for key in keys:
            if key == 'matchers':
                value = repr(self.matchers).replace('\n  ', '\n    ')
                value = value[:-2] + '  ])'
            elif key == 'response':
                value = repr(self._response)
                value = value[:-1] + '  )'
            else:
                value = repr(getattr(self, '_' + key))
            args.append('{}={}'.format(key, value))

        args = '(\n  {}\n)'.format(',\n  '.join(args))

        return type(self).__name__ + args

    def __enter__(self):
        """
        Implements context manager enter interface.
        """
        # Make mock persistent if using default times
        if self._times == 1:
            self._persist = True

        # Automatically enable the mock engine, if needed
        if not self._engine.active:
            self._engine.activate()
            self._disable_engine = True

        return self

    def __exit__(self, etype, value, traceback):
        """
        Implements context manager exit interface.
        """
        # Force disable mock
        self._times = 0

        # Automatically disable the mock engine, if needed
        if getattr(self, '_disable_engine', False):
            self._disable_engine = False
            self._engine.disable()

        if etype is not None:
            raise value


import functools
from asyncio import iscoroutinefunction, coroutine

def activate_async(fn, _engine):
    """
    Async version of activate decorator

    Arguments:
        fn (function): function that be wrapped by decorator.
        _engine (Engine): pook engine instance

    Returns:
        function: decorator wrapper function.
    """
    @coroutine
    @functools.wraps(fn)
    def wrapper(*args, **kw):
        _engine.activate()
        try:
            if iscoroutinefunction(fn):
                yield from fn(*args, **kw)  # noqa
            else:
                fn(*args, **kw)
        finally:
            _engine.disable()

    return wrapper


from functools import partial
from inspect import isfunction
from .mock import Mock
from .regex import isregex
from .mock_engine import MockEngine
from .exceptions import PookNoMatches, PookExpiredMock

class Engine(object):
    """
    Engine represents the mock interceptor and matcher engine responsible
    of triggering interceptors and match outgoing HTTP traffic.

    Arguments:
        network (bool, optional): enables/disables real networking mode.

    Attributes:
        debug (bool): enables/disables debug mode.
        active (bool): stores the current engine activation status.
        networking (bool): stores the current engine networking mode status.
        mocks (list[pook.Mock]): stores engine mocks.
        filters (list[function]): stores engine-level mock filter functions.
        mappers (list[function]): stores engine-level mock mapper functions.
        interceptors (list[pook.BaseInterceptor]): stores engine-level HTTP
            traffic interceptors.
        unmatched_reqs (list[pook.Request]): stores engine-level unmatched
            outgoing HTTP requests.
        network_filters (list[function]): stores engine-level real
            networking mode filters.
    """

    def __init__(self, network=False):
        # Enables/Disables debug mode.
        self.debug = True
        # Store the engine enable/disable status
        self.active = False
        # Enables/Disables real networking
        self.networking = network
        # Stores mocks
        self.mocks = []
        # Store engine-level global filters
        self.filters = []
        # Store engine-level global mappers
        self.mappers = []
        # Store unmatched requests.
        self.unmatched_reqs = []
        # Store network filters used to determine when a request
        # should be filtered or not.
        self.network_filters = []
        # Built-in mock engine to be used
        self.mock_engine = MockEngine(self)

    def set_mock_engine(self, engine):
        """
        Sets a custom mock engine, replacing the built-in one.

        This is particularly useful if you want to replace the built-in
        HTTP traffic mock interceptor engine with your custom one.

        For mock engine implementation details, see `pook.MockEngine`.

        Arguments:
            engine (pook.MockEngine): custom mock engine to use.
        """
        if not engine:
            raise TypeError('engine must be a valid object')

        # Instantiate mock engine
        mock_engine = engine(self)

        # Validate minimum viable interface
        methods = ('activate', 'disable')
        if not all([hasattr(mock_engine, method) for method in methods]):
            raise NotImplementedError('engine must implementent the '
                                      'required methods')

        # Use the custom mock engine
        self.mock_engine = mock_engine

        # Enable mock engine, if needed
        if self.active:
            self.mock_engine.activate()

    def enable_network(self, *hostnames):
        """
        Enables real networking mode, optionally passing one or multiple
        hostnames that would be used as filter.

        If at least one hostname matches with the outgoing traffic, the
        request will be executed via the real network.

        Arguments:
            *hostnames: optional list of host names to enable real network
                against them. hostname value can be a regular expression.
        """
        def hostname_filter(hostname, req):
            if isregex(hostname):
                return hostname.match(req.url.hostname)
            return req.url.hostname == hostname

        for hostname in hostnames:
            self.use_network_filter(partial(hostname_filter, hostname))

        self.networking = True

    def disable_network(self):
        """
        Disables real networking mode.
        """
        self.networking = False

    def use_network_filter(self, *fn):
        """
        Adds network filters to determine if certain outgoing unmatched
        HTTP traffic can stablish real network connections.

        Arguments:
            *fn (function): variadic function filter arguments to be used.
        """
        self.network_filters = self.network_filters + fn

    def flush_network_filters(self):
        """
        Flushes registered real networking filters in the current
        mock engine.
        """
        self.network_filters = []

    def mock(self, url=None, **kw):
        """
        Creates and registers a new HTTP mock in the current engine.

        Arguments:
            url (str): request URL to mock.
            activate (bool): force mock engine activation.
                Defaults to ``False``.
            **kw (mixed): variadic keyword arguments for ``Mock`` constructor.

        Returns:
            pook.Mock: new mock instance.
        """
        # Activate mock engine, if explicitly requested
        if kw.get('activate'):
            kw.pop('activate')
            self.activate()

        # Create the new HTTP mock expectation
        mock = Mock(url=url, **kw)
        # Expose current engine instance via mock
        mock._engine = self
        # Register the mock in the current engine
        self.add_mock(mock)

        # Return it for consumer satisfaction
        return mock

    def add_mock(self, mock):
        """
        Adds a new mock instance to the current engine.

        Arguments:
            mock (pook.Mock): mock instance to add.
        """
        self.mocks.append(mock)

    def remove_mock(self, mock):
        """
        Removes a specific mock instance by object reference.

        Arguments:
            mock (pook.Mock): mock instance to remove.
        """
        self.mocks = [m for m in self.mocks if m is not mock]

    def flush_mocks(self):
        """
        Flushes the current mocks.
        """
        self.mocks = []

    def _engine_proxy(self, method, *args, **kw):
        engine_method = getattr(self.mock_engine, method, None)

        if not engine_method:
            raise NotImplementedError('current mock engine does not implements'
                                      ' required "{}" method'.format(method))

        return engine_method(self.mock_engine, *args, **kw)

    def add_interceptor(self, *interceptors):
        """
        Adds one or multiple HTTP traffic interceptors to the current
        mocking engine.

        Interceptors are typically HTTP client specific wrapper classes that
        implements the pook interceptor interface.

        Note: this method is may not be implemented if using a custom mock
        engine.

        Arguments:
            interceptors (pook.interceptors.BaseInterceptor)
        """
        self._engine_proxy('add_interceptor', *interceptors)

    def flush_interceptors(self):
        """
        Flushes registered interceptors in the current mocking engine.

        This method is low-level. Only call it if you know what you are doing.

        Note: this method is may not be implemented if using a custom mock
        engine.
        """
        self._engine_proxy('flush_interceptors')

    def remove_interceptor(self, name):
        """
        Removes a specific interceptor by name.

        Note: this method is may not be implemented if using a custom mock
        engine.

        Arguments:
            name (str): interceptor name to disable.

        Returns:
            bool: `True` if the interceptor was disabled, otherwise `False`.
        """
        return self._engine_proxy('remove_interceptor', name)

    def activate(self):
        """
        Activates the registered interceptors in the mocking engine.

        This means any HTTP traffic captures by those interceptors will
        trigger the HTTP mock matching engine in order to determine if a given
        HTTP transaction should be mocked out or not.
        """
        if self.active:
            return None

        # Activate mock engine
        self.mock_engine.activate()
        # Enable engine state
        self.active = True

    def disable(self):
        """
        Disables interceptors and stops intercepting any outgoing HTTP traffic.
        """
        if not self.active:
            return None

        # Disable current mock engine
        self.mock_engine.disable()
        # Disable engine state
        self.active = False

    def reset(self):
        """
        Resets and flushes engine state and mocks to defaults.
        """
        # Reset engine
        Engine.__init__(self, network=self.networking)

    def unmatched_requests(self):
        """
        Returns a ``tuple`` of unmatched requests.

        Unmatched requests will be registered only if ``networking`` mode
        has been enabled.

        Returns:
            list: unmatched intercepted requests.
        """
        return [mock for mock in self.unmatched_reqs]

    def unmatched(self):
        """
        Returns the total number of unmatched requests intercepted by pook.

        Unmatched requests will be registered only if ``networking`` mode
        has been enabled.

        Returns:
            int: total number of unmatched requests.
        """
        return len(self.unmatched_requests())

    def isunmatched(self):
        """
        Returns ``True`` if there are unmatched requests. Otherwise ``False``.

        Unmatched requests will be registered only if ``networking`` mode
        has been enabled.

        Returns:
            bool
        """
        return len(self.unmatched()) > 0

    def pending(self):
        """
        Returns the number of pending mocks to be matched.

        Returns:
            int: number of pending mocks.
        """
        return len(self.pending_mocks())

    def pending_mocks(self):
        """
        Returns a ``tuple`` of pending mocks to be matched.

        Returns:
            tuple: pending mock instances.
        """
        return [mock for mock in self.mocks if not mock.isdone()]

    def ispending(self):
        """
        Returns the ``True`` if the engine has pending mocks to be matched.
        Otherwise ``False``.

        Returns:
            bool
        """
        return len(self.pending_mocks())

    def isactive(self):
        """
        Returns the current engine enabled/disabled status.

        Returns:
            bool: ``True`` if the engine is active. Otherwise ``False``.
        """
        return self.active

    def isdone(self):
        """
        Returns True if all the registered mocks has been triggered.

        Returns:
            bool: True is all the registered mocks are gone, otherwise False.
        """
        return all(mock.isdone() for mock in self.mocks)

    def _append(self, target, *fns):
        (target.append(fn) for fn in fns if isfunction(fn))

    def filter(self, *filters):
        """
        Append engine-level HTTP request filter functions.

        Arguments:
            filters*: variadic filter functions to be added.
        """
        self._append(self.filters, *filters)

    def map(self, *mappers):
        """
        Append engine-level HTTP request mapper functions.

        Arguments:
            filters*: variadic mapper functions to be added.
        """
        self._append(self.mappers, *mappers)

    def should_use_network(self, request):
        """
        Verifies if real networking mode should be used for the given
        request, passing it to the registered network filters.

        Arguments:
            request (pook.Request): outgoing HTTP request to test.

        Returns:
            bool
        """
        return (self.networking and
                all((fn(request) for fn in self.network_filters)))

    def match(self, request):
        """
        Matches a given Request instance contract against the registered mocks.

        If a mock passes all the matchers, its response will be returned.

        Arguments:
            request (pook.Request): Request contract to match.

        Raises:
            pook.PookNoMatches: if networking is disabled and no mock matches
                with the given request contract.

        Returns:
            pook.Response: the mock response to be used by the interceptor.
        """
        # Trigger engine-level request filters
        for test in self.filters:
            if not test(request, self):
                return False

        # Trigger engine-level request mappers
        for mapper in self.mappers:
            request = mapper(request, self)
            if not request:
                raise ValueError('map function must return a request object')

        # Store list of mock matching errors for further debugging
        match_errors = []

        # Try to match the request against registered mock definitions
        for mock in self.mocks[:]:
            try:
                # Return the first matched HTTP request mock
                matches, errors = mock.match(request.copy())
                if len(errors):
                    match_errors += errors
                if matches:
                    return mock
            except PookExpiredMock:
                # Remove the mock if already expired
                self.mocks.remove(mock)

        # Validate that we have a mock
        if not self.should_use_network(request):
            msg = 'pook error!\n\n'

            msg += (
                '=> Cannot match any mock for the '
                'following request:\n{}'.format(request)
            )

            # Compose unmatch error details, if debug mode is enabled
            if self.debug:
                err = '\n\n'.join([str(err) for err in match_errors])
                if err:
                    msg += '\n\n=> Detailed matching errors:\n{}\n'.format(err)

            # Raise no matches exception
            raise PookNoMatches(msg)

        # Register unmatched request
        self.unmatched_reqs.append(request)


import sys
import json as _json

from .regex import isregex
from .headers import HTTPHeaderDict
from .helpers import trigger_methods
from .matchers.url import protoregex

if sys.version_info < (3,):     # Python 2
    from urlparse import urlparse, parse_qs, urlunparse
else:                           # Python 3
    from urllib.parse import urlparse, parse_qs, urlunparse

class Request(object):
    """
    Request object representing the request mock expectation DSL.

    Arguments:
        method (str): HTTP method to match. Defaults to ``GET``.
        url (str): URL request to intercept and match.
        headers (dict): HTTP headers to match.
        query (dict): URL query params to match. Complementely to URL
            defined query params.
        body (str|regex): request body payload to match.
        json (str|dict|list): JSON payload body structure to match.
        xml (str): XML payload data structure to match.

    Attributes:
        method (str): HTTP method to match. Defaults to ``GET``.
        url (str): URL request to intercept and match.
        headers (dict): HTTP headers to match.
        query (dict): URL query params to match. Complementely to URL
            defined query params.
        body (str|regex): request body payload to match.
        json (str|dict|list): JSON payload body structure to match.
        xml (str): XML payload data structure to match.
    """

    # Store keys
    keys = ('method', 'headers', 'body', 'url', 'query')

    def __init__(self, method='GET', **kw):
        self._url = None
        self._body = None
        self._query = None
        self._method = method
        self._extra = kw.get('extra')
        self._headers = HTTPHeaderDict()

        trigger_methods(self, kw)

    @property
    def method(self):
        return self._method

    @method.setter
    def method(self, method):
        self._method = method

    @property
    def headers(self):
        return self._headers

    @headers.setter
    def headers(self, headers):
        if not hasattr(headers, '__setitem__'):
            raise TypeError('headers must be a dictionary')
        self._headers.extend(headers)

    @property
    def extra(self):
        return self._extra

    @extra.setter
    def extra(self, extra):
        if not isinstance(extra, dict):
            raise TypeError('extra must be a dictionary')
        self._extra = extra

    @property
    def url(self):
        return self._url

    @property
    def rawurl(self):
        return self._url if isregex(self._url) else urlunparse(self._url)

    @url.setter
    def url(self, url):
        if isregex(url):
            self._url = url
        else:
            if not protoregex.match(url):
                url = 'http://{}'.format(url)
            self._url = urlparse(url)
            self._query = (parse_qs(self._url.query)
                           if self._url.query else self._query)

    @property
    def query(self, url):
        return self._query

    @query.setter
    def query(self, params):
        self._query = parse_qs(params)

    @property
    def body(self):
        return self._body

    @body.setter
    def body(self, body):
        if hasattr(body, 'decode'):
            try:
                body = body.decode('utf-8', 'strict')
            except Exception:
                pass

        self._body = body

    @property
    def json(self):
        return _json.loads(self._body)

    @json.setter
    def json(self, data):
        if isinstance(data, str):
            self._body = data
        else:
            self._body = _json.dumps(data)

    @property
    def xml(self):
        return self._body

    @xml.setter
    def xml(self, data):
        self._body = data

    def copy(self):
        """
        Copies the current Request object instance for side-effects purposes.

        Returns:
            pook.Request: copy of the current Request instance.
        """
        req = type(self)()
        req.__dict__ = self.__dict__.copy()
        req._headers = self.headers.copy()
        return req

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

        Returns:
            str
        """
        entries = []

        entries.append('Method: {}'.format(self._method))
        entries.append('URL: {}'.format(
            self._url if isregex(self._url) else self.rawurl))

        if self._query:
            entries.append('Query: {}'.format(self._query))

        if self._headers:
            entries.append('Headers: {}'.format(self._headers))

        if self._body:
            entries.append('Body: {}'.format(self._body))

        separator = '=' * 50
        return (separator + '\n{}\n' + separator).format('\n'.join(entries))


import functools
import re
from contextlib import contextmanager
from inspect import isfunction

from .engine import Engine
from .matcher import MatcherEngine
from .mock import Mock
from .mock_engine import MockEngine
from .request import Request
from .response import Response

try:
    from asyncio import iscoroutinefunction
except ImportError:
    iscoroutinefunction = None
if iscoroutinefunction is not None:
    from .activate_async import activate_async
else:
    activate_async = None

# Public API symbols to export
__all__ = (
    'activate', 'on', 'disable', 'off', 'reset', 'engine',
    'use_network', 'enable_network', 'disable_network',
    'get', 'post', 'put', 'patch', 'head', 'use',
    'set_mock_engine', 'delete', 'options', 'pending',
    'ispending', 'mock', 'pending_mocks', 'unmatched_requests',
    'isunmatched', 'unmatched', 'isactive', 'isdone', 'regex',
    'Engine', 'Mock', 'Request', 'Response',
    'MatcherEngine', 'MockEngine'
)

# Default singleton mock engine to be used
_engine = Engine()

def debug(enable=True):
    """
    Enables or disables debug mode in the current mock engine.

    Arguments:
        enable (bool): ``True`` to enable debug mode. Otherwise ``False``.
    """
    _engine.debug = enable

def engine():
    """
    Returns the current running mock engine.

    Returns:
        pook.Engine: current used engine.
    """
    return _engine

def set_mock_engine(engine):
    """
    Sets a custom mock engine, replacing the built-in one.

    This is particularly useful if you want to replace the built-in
    HTTP traffic mock interceptor engine with your custom one.

    For mock engine implementation details, see `pook.MockEngine`.

    Arguments:
        engine (pook.MockEngine): custom mock engine to use.
    """
    _engine.set_mock_engine(engine)

def activate(fn=None):
    """
    Enables the HTTP traffic interceptors.

    This function can be used as decorator.

    Arguments:
        fn (function|coroutinefunction): Optional function argument
            if used as decorator.

    Returns:
        function: decorator wrapper function, only if called as decorator,
            otherwise ``None``.

    Example::

        # Standard use case
        pook.activate()
        pook.mock('server.com/foo').reply(404)

        res = requests.get('server.com/foo')
        assert res.status_code == 404
        pook.disable()

        # Decorator use case
        @pook.activate
        def test_request():
            pook.mock('server.com/foo').reply(404)

            res = requests.get('server.com/foo')
            assert res.status_code == 404
    """
    # If not used as decorator, activate the engine and exit
    if not isfunction(fn):
        _engine.activate()
        return None

    # If used as decorator for an async coroutine, wrap it
    if iscoroutinefunction is not None and iscoroutinefunction(fn):
        return activate_async(fn, _engine)

    @functools.wraps(fn)
    def wrapper(*args, **kw):
        _engine.activate()
        try:
            fn(*args, **kw)
        finally:
            _engine.disable()

    return wrapper

def on(fn=None):
    """
    Enables the HTTP traffic interceptors.
    Alias to ``pook.activate()``.

    Arguments:
        fn (function|coroutinefunction): Optional function argument
            if used as decorator.

    Returns:
        function: decorator wrapper function, only if called as decorator.

    Example::

        # Standard usage
        pook.on()
        pook.mock('server.com/foo').reply(404)

        res = requests.get('server.com/foo')
        assert res.status_code == 404

        # Usage as decorator
        @pook.on
        def test_request():
            pook.mock('server.com/foo').reply(404)

            res = requests.get('server.com/foo')
            assert res.status_code == 404
    """
    return activate(fn)

def disable():
    """
    Disables HTTP traffic interceptors.
    """
    _engine.disable()

def off():
    """
    Disables mock engine, HTTP traffic interceptors and flushed all the
    registered mocks.

    Internally, it calls ``pook.disable()`` and ``pook.off()``.
    """
    disable()
    reset()

def reset():
    """
    Resets current mock engine state, flushing all the registered mocks.

    This action will not disable the mock engine.
    """
    _engine.reset()

@contextmanager
def use(network=False):
    """
    Creates a new isolated mock engine to be used via context manager.

    Example::

        with pook.use() as engine:
            pook.mock('server.com/foo').reply(404)

            res = requests.get('server.com/foo')
            assert res.status_code == 404
    """
    global _engine

    # Create temporal engine
    __engine = _engine
    activated = __engine.active
    if activated:
        __engine.disable()

    _engine = Engine(network=network)
    _engine.activate()

    # Yield enfine to be used by the context manager
    yield _engine

    # Restore engine state
    _engine.disable()
    if network:
        _engine.disable_network()

    # Restore previous engine
    _engine = __engine
    if activated:
        _engine.activate()

@contextmanager
def context(network=False):
    """
    Create a new isolated mock engine to be used via context manager.

    Semantic alias to ``pook.context()``.

    Example::

        with pook.use() as engine:
            pook.mock('server.com/foo').reply(404)

            res = requests.get('server.com/foo')
            assert res.status_code == 404
    """
    with use(network=network) as engine:
        yield engine

@contextmanager
def use_network():
    """
    Creates a new mock engine to be used as context manager

    Example::

        with pook.use_network() as engine:
            pook.mock('server.com/foo').reply(404)

            res = requests.get('server.com/foo')
            assert res.status_code == 404
    """
    with use(network=True) as engine:
        yield engine

def enable_network(*hostnames):
    """
    Enables real networking mode for unmatched mocks in the current
    mock engine.
    """
    _engine.enable_network(*hostnames)

def disable_network():
    """
    Disables real traffic networking mode in the current mock engine.
    """
    _engine.disable_network()

def use_network_filter(*fn):
    """
    Adds network filters to determine if certain outgoing
    unmatched HTTP traffic can stablish real network connections.

    Arguments:
        *fn (function): variadic function filter arguments to be used.
    """
    _engine.use_network_filter(*fn)

def flush_network_filters():
    """
    Flushes registered real networking filters in the current
    mock engine.
    """
    _engine.flush_network_filters()

def mock(url=None, **kw):
    """
    Creates and register a new HTTP mock.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic keyword arguments.

    Returns:
        pook.Mock: mock instance
    """
    return _engine.mock(url, **kw)

def get(url, **kw):
    """
    Registers a new mock HTTP request with GET method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: mock instance
    """
    return mock(url, method='GET', **kw)

def post(url, **kw):
    """
    Registers a new mock HTTP request with POST method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: mock instance
    """
    return mock(url, method='POST', **kw)

def put(url, **kw):
    """
    Registers a new mock HTTP request with PUT method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: mock instance
    """
    return mock(url, method='PUT', **kw)

def delete(url, **kw):
    """
    Registers a new mock HTTP request with DELETE method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: mock instance
    """
    return mock(url, method='DELETE', **kw)

def head(url, **kw):
    """
    Registers a new mock HTTP request with HEAD method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: mock instance
    """
    return mock(url, method='HEAD', **kw)

def patch(url=None, **kw):
    """
    Registers a new mock HTTP request with PATCH method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: new mock instance.
    """
    return mock(url, method='PATCH', **kw)

def options(url=None, **kw):
    """
    Registers a new mock HTTP request with OPTIONS method.

    Arguments:
        url (str): request URL to mock.
        activate (bool): force mock engine activation.
            Defaults to ``False``.
        **kw (mixed): variadic arguments to ``pook.Mock`` constructor.

    Returns:
        pook.Mock: new mock instance.
    """
    return mock(url, method='OPTIONS', **kw)

def pending():
    """
    Returns the numbers of pending mocks to be matched.

    Returns:
        int: number of pending mocks to match.
    """
    return _engine.pending()

def ispending():
    """
    Returns the numbers of pending mocks to be matched.

    Returns:
        int: number of pending mocks to match.
    """
    return _engine.ispending()

def pending_mocks():
    """
    Returns pending mocks to be matched.

    Returns:
        list: pending mock instances.
    """
    return _engine.pending_mocks()

def unmatched_requests():
    """
    Returns a ``tuple`` of unmatched requests.

    Unmatched requests will be registered only if ``networking``
    mode has been enabled.

    Returns:
        list: unmatched intercepted requests.
    """
    return _engine.unmatched_requests()

def unmatched():
    """
    Returns the total number of unmatched requests intercepted by pook.

    Unmatched requests will be registered only if ``networking``
    mode has been enabled.

    Returns:
        int: total number of unmatched requests.
    """
    return _engine.unmatched()

def isunmatched():
    """
    Returns ``True`` if there are unmatched requests. Otherwise ``False``.

    Unmatched requests will be registered only if ``networking``
    mode has been enabled.

    Returns:
        bool
    """
    return _engine.isunmatched()

def isactive():
    """
    Returns ``True`` if pook is active and intercepting traffic.
    Otherwise ``False``.

    Returns:
        bool: True is all the registered mocks are gone, otherwise False.
    """
    return _engine.isactive()

def isdone():
    """
    Returns True if all the registered mocks has been triggered.

    Returns:
        bool: True is all the registered mocks are gone, otherwise False.
    """
    return _engine.isdone()

def regex(expression, flags=re.IGNORECASE):
    """
    Convenient shortcut to ``re.compile()`` for fast, easy to use
    regular expression compilation without an extra import statement.

    Arguments:
        expression (str): regular expression value.
        flags (int): optional regular expression flags.
            Defaults to ``re.IGNORECASE``

    Returns:
        expression (str): string based regular expression.

    Raises:
        Exception: in case of regular expression compilation error

    Example::

        (pook
            .get('api.com/foo')
            .header('Content-Type', pook.regex('[a-z]{1,4}')))
    """
    return re.compile(expression, flags=flags)


from .interceptors import interceptors

class MockEngine(object):
    """
    ``MockEngine`` represents the low-level mocking engine abstraction
    layer between ``pook`` and the underlying mocking mechanism
    responsible of intercepting and trigger outgoing HTTP traffic within
    the Python runtime.

    ``MockEngine`` implements the built-in `pook` mock engine based on HTTP
    interceptors strategy.

    Developers can implement and plug in their own ``MockEngine`` in order
    to fit custom mocking logic needs.

    You can see a custom ``MockEngine`` implementation here:
    http://bit.ly/2EymMro

    Custom mock engines must implementent at least the following methods:

    - `engine.__init__(self, engine)`
    - `engine.activate(self)`
    - `engine.disable(self)`

    Custom mock engines can optionally implement the following methods:

    - `engine.add_interceptors(self, *interceptors)`
    - `engine.flush_interceptors(self)`
    - `engine.disable_interceptor(self, name) -> bool`

    Arguments:
        engine (pook.Engine): injected pook engine to be used.

    Attributes:
        engine (pook.Engine): stores pook engine to be used.
        interceptors (list[pook.BaseInterceptor]): stores engine-level HTTP
            traffic interceptors.
    """

    def __init__(self, engine):
        # Store pook engine
        self.engine = engine
        # Store HTTP client interceptors
        self.interceptors = []
        # Self-register built-in interceptors
        self.add_interceptor(*interceptors)

    def add_interceptor(self, *interceptors):
        """
        Adds one or multiple HTTP traffic interceptors to the current
        mocking engine.

        Interceptors are typically HTTP client specific wrapper classes that
        implements the pook interceptor interface.

        Arguments:
            interceptors (pook.interceptors.BaseInterceptor)
        """
        for interceptor in interceptors:
            self.interceptors.append(interceptor(self.engine))

    def flush_interceptors(self):
        """
        Flushes registered interceptors in the current mocking engine.

        This method is low-level. Only call it if you know what you are doing.
        """
        self.interceptors = []

    def remove_interceptor(self, name):
        """
        Removes a specific interceptor by name.

        Arguments:
            name (str): interceptor name to disable.

        Returns:
            bool: `True` if the interceptor was disabled, otherwise `False`.
        """
        for index, interceptor in enumerate(self.interceptors):
            matches = (
                type(interceptor).__name__ == name or
                getattr(interceptor, 'name') == name
            )
            if matches:
                self.interceptors.pop(index)
                return True
        return False

    def activate(self):
        """
        Activates the registered interceptors in the mocking engine.

        This means any HTTP traffic captures by those interceptors will
        trigger the HTTP mock matching engine in order to determine if a given
        HTTP transaction should be mocked out or not.
        """
        [interceptor.activate() for interceptor in self.interceptors]

    def disable(self):
        """
        Disables interceptors and stops intercepting any outgoing HTTP traffic.
        """
        # Restore HTTP interceptors
        for interceptor in self.interceptors:
            try:
                interceptor.disable()
            except RuntimeError:
                pass  # explicitely ignore runtime patch errors


from django.conf import settings
from django.db.models.expressions import Col
from django.utils.functional import cached_property

from pgcrypto import (
    PGP_PUB_DECRYPT_SQL,
    PGP_PUB_ENCRYPT_SQL,
    PGP_SYM_DECRYPT_SQL,
    PGP_SYM_ENCRYPT_SQL,
)

def get_setting(connection, key):
    """Get key from connection or default to settings."""
    if key in connection.settings_dict:
        return connection.settings_dict[key]
    else:
        return getattr(settings, key)

class DecryptedCol(Col):
    """Provide DecryptedCol support without using `extra` sql."""

    def __init__(self, alias, target, output_field=None):
        """Init the decryption."""
        self.target = target

        super(DecryptedCol, self).__init__(alias, target, output_field)

    def as_sql(self, compiler, connection):
        """Build SQL with decryption and casting."""
        sql, params = super(DecryptedCol, self).as_sql(compiler, connection)
        sql = self.target.get_decrypt_sql(connection) % (sql, self.target.get_cast_sql())
        return sql, params

class HashMixin:
    """Keyed hash mixin.

    `HashMixin` uses 'pgcrypto' to encrypt data in a postgres database.
    """
    encrypt_sql = None  # Set in implementation class

    def __init__(self, original=None, *args, **kwargs):
        """Tells the init the original attr."""
        self.original = original

        super(HashMixin, self).__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        """Save the original_value."""
        if self.original:
            original_value = getattr(model_instance, self.original)
            setattr(model_instance, self.attname, original_value)

        return super(HashMixin, self).pre_save(model_instance, add)

    def get_placeholder(self, value=None, compiler=None, connection=None):
        """
        Tell postgres to encrypt this field with a hashing function.

        The `value` string is checked to determine if we need to hash or keep
        the current value.

        `compiler` and `connection` is ignored here as we don't need custom operators.
        """
        if value is None or value.startswith('\\x'):
            return '%s'

        return self.get_encrypt_sql(connection)

    def get_encrypt_sql(self, connection):
        """Get encrypt sql. This may be overidden by some implementations."""
        return self.encrypt_sql

class PGPMixin:
    """PGP encryption for field's value.

    `PGPMixin` uses 'pgcrypto' to encrypt data in a postgres database.
    """
    encrypt_sql = None  # Set in implementation class
    decrypt_sql = None  # Set in implementation class
    cast_type = None

    def __init__(self, *args, **kwargs):
        """`max_length` should be set to None as encrypted text size is variable."""
        super().__init__(*args, **kwargs)

    def db_type(self, connection=None):
        """Value stored in the database is hexadecimal."""
        return 'bytea'

    def get_placeholder(self, value, compiler, connection):
        """Tell postgres to encrypt this field using PGP."""
        raise NotImplementedError('The `get_placeholder` needs to be implemented.')

    def get_cast_sql(self):
        """Get cast sql. This may be overidden by some implementations."""
        return self.cast_type

    def get_decrypt_sql(self, connection):
        """Get decrypt sql."""
        raise NotImplementedError('The `get_decrypt_sql` needs to be implemented.')

    def get_col(self, alias, output_field=None):
        """Get the decryption for col."""
        if output_field is None:
            output_field = self
        if alias != self.model._meta.db_table or output_field != self:
            return DecryptedCol(
                alias,
                self,
                output_field
            )
        else:
            return self.cached_col

    @cached_property
    def cached_col(self):
        """Get cached version of decryption for col."""
        return DecryptedCol(
            self.model._meta.db_table,
            self
        )

class PGPPublicKeyFieldMixin(PGPMixin):
    """PGP public key encrypted field mixin for postgres."""
    encrypt_sql = PGP_PUB_ENCRYPT_SQL
    decrypt_sql = PGP_PUB_DECRYPT_SQL
    cast_type = 'TEXT'

    def get_placeholder(self, value=None, compiler=None, connection=None):
        """Tell postgres to encrypt this field using PGP."""
        return self.encrypt_sql.format(get_setting(connection, 'PUBLIC_PGP_KEY'))

    def get_decrypt_sql(self, connection):
        """Get decrypt sql."""
        return self.decrypt_sql.format(get_setting(connection, 'PRIVATE_PGP_KEY'))

class PGPSymmetricKeyFieldMixin(PGPMixin):
    """PGP symmetric key encrypted field mixin for postgres."""
    encrypt_sql = PGP_SYM_ENCRYPT_SQL
    decrypt_sql = PGP_SYM_DECRYPT_SQL
    cast_type = 'TEXT'

    def get_placeholder(self, value, compiler, connection):
        """Tell postgres to encrypt this field using PGP."""
        return self.encrypt_sql.format(get_setting(connection, 'PGCRYPTO_KEY'))

    def get_decrypt_sql(self, connection):
        """Get decrypt sql."""
        return self.decrypt_sql.format(get_setting(connection, 'PGCRYPTO_KEY'))

class DecimalPGPFieldMixin:
    """Decimal PGP encrypted field mixin for postgres."""
    cast_type = 'NUMERIC(%(max_digits)s, %(decimal_places)s)'

    def get_cast_sql(self):
        """Get cast sql."""
        return self.cast_type % {
            'max_digits': self.max_digits,
            'decimal_places': self.decimal_places
        }


import codecs
import yaml
from yaml.composer import Composer
from ansiblereview import Result, Error

def hunt_repeated_yaml_keys(data):
    """Parses yaml and returns a list of repeated variables and
       the line on which they occur
    """
    loader = yaml.Loader(data)

    def compose_node(parent, index):
        # the line number where the previous token has ended (plus empty lines)
        line = loader.line
        node = Composer.compose_node(loader, parent, index)
        node.__line__ = line + 1
        return node

    def construct_mapping(node, deep=False):
        mapping = dict()
        errors = dict()
        for key_node, value_node in node.value:
            key = key_node.value
            if key in mapping:
                if key in errors:
                    errors[key].append(key_node.__line__)
                else:
                    errors[key] = [mapping[key], key_node.__line__]

            mapping[key] = key_node.__line__

        return errors

    loader.compose_node = compose_node
    loader.construct_mapping = construct_mapping
    data = loader.get_single_data()
    return data

def repeated_vars(candidate, settings):
    with codecs.open(candidate.path, 'r') as f:
        errors = hunt_repeated_yaml_keys(f) or dict()
    return Result(candidate, [Error(err_line, "Variable %s occurs more than once" % err_key)
                              for err_key in errors for err_line in errors[err_key]])