# Third party
from ddtrace.vendor import wrapt

# Project
from ...compat import PY2, httplib, parse
from ...constants import ANALYTICS_SAMPLE_RATE_KEY
from ...ext import SpanTypes, http as ext_http
from ...http import store_request_headers, store_response_headers
from ...internal.logger import get_logger
from ...pin import Pin
from ...propagation.http import HTTPPropagator
from ...settings import config
from ...utils.formats import asbool, get_env
from ...utils.wrappers import unwrap as _u

span_name = 'httplib.request' if PY2 else 'http.client.request'

log = get_logger(__name__)


config._add('httplib', {
    'distributed_tracing': asbool(get_env('httplib', 'distributed_tracing', default=False)),
})


def _wrap_init(func, instance, args, kwargs):
    Pin(app='httplib', service=None, _config=config.httplib).onto(instance)
    return func(*args, **kwargs)


def _wrap_getresponse(func, instance, args, kwargs):
    # Use any attached tracer if available, otherwise use the global tracer
    pin = Pin.get_from(instance)
    if not pin or not pin.enabled():
        return func(*args, **kwargs)

    resp = None
    try:
        resp = func(*args, **kwargs)
        return resp
    finally:
        try:
            # Get the span attached to this instance, if available
            span = getattr(instance, '_datadog_span', None)
            if span:
                if resp:
                    span.set_tag(ext_http.STATUS_CODE, resp.status)
                    span.error = int(500 <= resp.status)
                    store_response_headers(dict(resp.getheaders()), span, config.httplib)

                span.finish()
                delattr(instance, '_datadog_span')
        except Exception:
            log.debug('error applying request tags', exc_info=True)


def _wrap_request(func, instance, args, kwargs):
    # Use any attached tracer if available, otherwise use the global tracer
    pin = Pin.get_from(instance)
    if should_skip_request(pin, instance):
        return func(*args, **kwargs)

    cfg = config.get_from(instance)

    try:
        # Create a new span and attach to this instance (so we can retrieve/update/close later on the response)
        span = pin.tracer.trace(span_name, span_type=SpanTypes.HTTP)
        setattr(instance, '_datadog_span', span)

        # propagate distributed tracing headers
        if cfg.get("distributed_tracing"):
            if len(args) > 3:
                headers = args[3]
            else:
                headers = kwargs.setdefault('headers', {})
            propagator = HTTPPropagator()
            propagator.inject(span.context, headers)

    except Exception:
        log.debug('error configuring request', exc_info=True)
    return func(*args, **kwargs)


def _wrap_putrequest(func, instance, args, kwargs):
    # Use any attached tracer if available, otherwise use the global tracer
    pin = Pin.get_from(instance)
    if should_skip_request(pin, instance):
        return func(*args, **kwargs)

    try:
        if hasattr(instance, '_datadog_span'):
            # Reuse an existing span set in _wrap_request
            span = instance._datadog_span
        else:
            # Create a new span and attach to this instance (so we can retrieve/update/close later on the response)
            span = pin.tracer.trace(span_name, span_type=SpanTypes.HTTP)
            setattr(instance, '_datadog_span', span)

        method, path = args[:2]
        scheme = 'https' if isinstance(instance, httplib.HTTPSConnection) else 'http'
        port = ':{port}'.format(port=instance.port)

        if (scheme == 'http' and instance.port == 80) or (scheme == 'https' and instance.port == 443):
            port = ''
        url = '{scheme}://{host}{port}{path}'.format(scheme=scheme, host=instance.host, port=port, path=path)

        # sanitize url
        parsed = parse.urlparse(url)
        sanitized_url = parse.urlunparse((
            parsed.scheme,
            parsed.netloc,
            parsed.path,
            parsed.params,
            None,  # drop query
            parsed.fragment
        ))

        span.set_tag(ext_http.URL, sanitized_url)
        span.set_tag(ext_http.METHOD, method)
        if config.httplib.trace_query_string:
            span.set_tag(ext_http.QUERY_STRING, parsed.query)

        # set analytics sample rate
        span.set_tag(
            ANALYTICS_SAMPLE_RATE_KEY,
            config.httplib.get_analytics_sample_rate()
        )
    except Exception:
        log.debug('error applying request tags', exc_info=True)
    return func(*args, **kwargs)


def _wrap_putheader(func, instance, args, kwargs):
    span = getattr(instance, '_datadog_span', None)
    if span:
        store_request_headers({args[0]: args[1]}, span, config.httplib)

    return func(*args, **kwargs)


def should_skip_request(pin, request):
    """Helper to determine if the provided request should be traced"""
    if not pin or not pin.enabled():
        return True

    api = pin.tracer.writer.api
    return request.host == api.hostname and request.port == api.port


def patch():
    """ patch the built-in urllib/httplib/httplib.client methods for tracing"""
    if getattr(httplib, '__datadog_patch', False):
        return
    setattr(httplib, '__datadog_patch', True)

    # Patch the desired methods
    setattr(httplib.HTTPConnection, '__init__',
            wrapt.FunctionWrapper(httplib.HTTPConnection.__init__, _wrap_init))
    setattr(httplib.HTTPConnection, 'getresponse',
            wrapt.FunctionWrapper(httplib.HTTPConnection.getresponse, _wrap_getresponse))
    setattr(httplib.HTTPConnection, 'request',
            wrapt.FunctionWrapper(httplib.HTTPConnection.request, _wrap_request))
    setattr(httplib.HTTPConnection, 'putrequest',
            wrapt.FunctionWrapper(httplib.HTTPConnection.putrequest, _wrap_putrequest))
    setattr(httplib.HTTPConnection, 'putheader',
            wrapt.FunctionWrapper(httplib.HTTPConnection.putheader, _wrap_putheader))


def unpatch():
    """ unpatch any previously patched modules """
    if not getattr(httplib, '__datadog_patch', False):
        return
    setattr(httplib, '__datadog_patch', False)

    _u(httplib.HTTPConnection, '__init__')
    _u(httplib.HTTPConnection, 'getresponse')
    _u(httplib.HTTPConnection, 'request')
    _u(httplib.HTTPConnection, 'putrequest')
    _u(httplib.HTTPConnection, 'putheader')
