from apistar.main import get_current_app
from click.testing import CliRunner
from urllib.parse import urlparse
import requests
import io
import pytest


class HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict):
    def get_all(self, key, default):
        return self.getheaders(key)


class MockOriginalResponse(object):
    """
    A mock urllib3.Response object.
    """
    def __init__(self, headers):
        self.msg = HeaderDict(headers)
        self.closed = False

    def isclosed(self):
        return self.closed

    def close(self):
        self.closed = True


class WSGIAdapter(requests.adapters.HTTPAdapter):
    """
    A transport adapter for `requests` that makes requests directly to a
    WSGI app, rather than making actual HTTP requests over the network.
    """
    def __init__(self, wsgi_app, root_path=None):
        self.wsgi_app = wsgi_app
        self.root_path = ('/' + root_path.strip('/')) if root_path else ''

    def get_environ(self, request):
        """
        Given a `requests.PreparedRequest` instance, return a WSGI environ dict.
        """
        url_components = urlparse(request.url)
        environ = {
            'REQUEST_METHOD': request.method,
            'wsgi.url_scheme': url_components.scheme,
            'SCRIPT_NAME': self.root_path,
            'PATH_INFO': url_components.path,
            'QUERY_STRING': url_components.query
        }

        if url_components.port:
            environ['SERVER_NAME'] = url_components.hostname
            environ['SERVER_PORT'] = str(url_components.port)
        else:
            environ['HTTP_HOST'] =  url_components.hostname

        for key, value in request.headers.items():
            key = key.upper().replace('-', '_')
            if key not in ('CONTENT_LENGTH', 'CONTENT_TYPE'):
                key = 'HTTP_' + key
            environ[key] = value

        return environ

    def send(self, request, *args, **kwargs):
        """
        Make an outgoing request to a WSGI application.
        """
        raw_kwargs = {}

        def start_response(wsgi_status, wsgi_headers):
            status, _, reason = wsgi_status.partition(' ')
            raw_kwargs['status'] = int(status)
            raw_kwargs['reason'] = reason
            raw_kwargs['headers'] = wsgi_headers
            raw_kwargs['version'] = 11
            raw_kwargs['preload_content'] = False
            raw_kwargs['original_response'] = MockOriginalResponse(wsgi_headers)

        # Make the outgoing request via WSGI.
        environ = self.get_environ(request)
        wsgi_response = self.wsgi_app(environ, start_response)

        # Build the underlying urllib3.HTTPResponse
        raw_kwargs['body'] = io.BytesIO(b''.join(wsgi_response))
        raw = requests.packages.urllib3.HTTPResponse(**raw_kwargs)

        # Build the requests.Response
        return self.build_response(request, raw)

    def close(self):
        pass


class _TestClient(requests.Session):
    def __init__(self, app=None, root_path=None):
        super(_TestClient, self).__init__()
        if app is None:
            app = get_current_app()
        adapter = WSGIAdapter(app.wsgi, root_path=root_path)
        self.mount('http://', adapter)
        self.mount('https://', adapter)
        self.headers.update({'User-Agent': 'requests_client'})


def TestClient(*args, **kwargs):
    """
    We have to work around py.test discovery attempting to pick up
    the `TestClient` class, by declaring this as a function.
    """
    return _TestClient(*args, **kwargs)


class CommandLineRunner(CliRunner):
    def __init__(self, app):
        self.click_client = app.click
        super(CommandLineRunner, self).__init__()

    def invoke(self, *args, **kwargs):
        args = [self.click_client] + list(args)
        return super(CommandLineRunner, self).invoke(*args, **kwargs)
