import warnings
import sys
import errno
import functools
import logging
import socket
import ssl
import os

import pytest
try:
    import brotli
except ImportError:
    brotli = None

from urllib3.exceptions import HTTPWarning
from urllib3.packages import six
from urllib3.util import ssl_

# We need a host that will not immediately close the connection with a TCP
# Reset. SO suggests this hostname
TARPIT_HOST = '10.255.255.1'

# (Arguments for socket, is it IPv6 address?)
VALID_SOURCE_ADDRESSES = [(('::1', 0), True), (('127.0.0.1', 0), False)]
# RFC 5737: 192.0.2.0/24 is for testing only.
# RFC 3849: 2001:db8::/32 is for documentation only.
INVALID_SOURCE_ADDRESSES = [('192.0.2.255', 0), ('2001:db8::1', 0)]


def clear_warnings(cls=HTTPWarning):
    new_filters = []
    for f in warnings.filters:
        if issubclass(f[2], cls):
            continue
        new_filters.append(f)
    warnings.filters[:] = new_filters


def setUp():
    clear_warnings()
    warnings.simplefilter('ignore', HTTPWarning)


def onlyPy279OrNewer(test):
    """Skips this test unless you are on Python 2.7.9 or later."""

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        msg = "{name} requires Python 2.7.9+ to run".format(name=test.__name__)
        if sys.version_info < (2, 7, 9):
            pytest.skip(msg)
        return test(*args, **kwargs)
    return wrapper


def onlyPy2(test):
    """Skips this test unless you are on Python 2.x"""

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        msg = "{name} requires Python 2.x to run".format(name=test.__name__)
        if six.PY3:
            pytest.skip(msg)
        return test(*args, **kwargs)
    return wrapper


def onlyPy3(test):
    """Skips this test unless you are on Python3.x"""

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        msg = "{name} requires Python3.x to run".format(name=test.__name__)
        if not six.PY3:
            pytest.skip(msg)
        return test(*args, **kwargs)
    return wrapper


def onlyBrotlipy():
    return pytest.mark.skipif(
        brotli is None, reason='only run if brotlipy is present')


def notBrotlipy():
    return pytest.mark.skipif(
        brotli is not None, reason='only run if brotlipy is absent')


def notSecureTransport(test):
    """Skips this test when SecureTransport is in use."""

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        msg = "{name} does not run with SecureTransport".format(name=test.__name__)
        if ssl_.IS_SECURETRANSPORT:
            pytest.skip(msg)
        return test(*args, **kwargs)
    return wrapper


def notOpenSSL098(test):
    """Skips this test for Python 3.4 and 3.5 macOS python.org distributions"""

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        is_stdlib_ssl = not ssl_.IS_SECURETRANSPORT and not ssl_.IS_PYOPENSSL
        if is_stdlib_ssl and ssl.OPENSSL_VERSION == "OpenSSL 0.9.8zh 14 Jan 2016":
            pytest.xfail("{name} fails with OpenSSL 0.9.8zh".format(name=test.__name__))
        return test(*args, **kwargs)
    return wrapper


_requires_network_has_route = None


def requires_network(test):
    """Helps you skip tests that require the network"""

    def _is_unreachable_err(err):
        return getattr(err, 'errno', None) in (errno.ENETUNREACH,
                                               errno.EHOSTUNREACH)  # For OSX

    def _has_route():
        try:
            sock = socket.create_connection((TARPIT_HOST, 80), 0.0001)
            sock.close()
            return True
        except socket.timeout:
            return True
        except socket.error as e:
            if _is_unreachable_err(e):
                return False
            else:
                raise

    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        global _requires_network_has_route

        if _requires_network_has_route is None:
            _requires_network_has_route = _has_route()

        if _requires_network_has_route:
            return test(*args, **kwargs)
        else:
            msg = "Can't run {name} because the network is unreachable".format(
                name=test.__name__)
            pytest.skip(msg)
    return wrapper


def requires_ssl_context_keyfile_password(test):
    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        if ((not ssl_.IS_PYOPENSSL and sys.version_info < (2, 7, 9))
                or ssl_.IS_SECURETRANSPORT):
            pytest.skip("%s requires password parameter for "
                        "SSLContext.load_cert_chain()" % test.__name__)
        return test(*args, **kwargs)
    return wrapper


def fails_on_travis_gce(test):
    """Expect the test to fail on Google Compute Engine instances for Travis.
    Travis uses GCE for its sudo: enabled builds.

    Reason for this decorator:
    https://github.com/urllib3/urllib3/pull/1475#issuecomment-440788064
    """
    @functools.wraps(test)
    def wrapper(*args, **kwargs):
        if os.environ.get("TRAVIS_INFRA") == "gce":
            pytest.xfail("%s is expected to fail on Travis GCE builds" % test.__name__)
        return test(*args, **kwargs)
    return wrapper


def requiresTLSv1():
    """Test requires TLSv1 available"""
    return pytest.mark.skipif(not hasattr(ssl, "PROTOCOL_TLSv1"), reason="Test requires TLSv1")


def requiresTLSv1_1():
    """Test requires TLSv1.1 available"""
    return pytest.mark.skipif(not hasattr(ssl, "PROTOCOL_TLSv1_1"), reason="Test requires TLSv1.1")


def requiresTLSv1_2():
    """Test requires TLSv1.2 available"""
    return pytest.mark.skipif(not hasattr(ssl, "PROTOCOL_TLSv1_2"), reason="Test requires TLSv1.2")


def requiresTLSv1_3():
    """Test requires TLSv1.3 available"""
    return pytest.mark.skipif(
        not getattr(ssl, "HAS_TLSv1_3", False), reason="Test requires TLSv1.3"
    )


class _ListHandler(logging.Handler):
    def __init__(self):
        super(_ListHandler, self).__init__()
        self.records = []

    def emit(self, record):
        self.records.append(record)


class LogRecorder(object):
    def __init__(self, target=logging.root):
        super(LogRecorder, self).__init__()
        self._target = target
        self._handler = _ListHandler()

    @property
    def records(self):
        return self._handler.records

    def install(self):
        self._target.addHandler(self._handler)

    def uninstall(self):
        self._target.removeHandler(self._handler)

    def __enter__(self):
        self.install()
        return self.records

    def __exit__(self, exc_type, exc_value, traceback):
        self.uninstall()
        return False
