# -*- coding: utf-8 -*-
"""
hyper/cli
~~~~~~~~~

Command line interface for Hyper inspired by Httpie.
"""
import json
import locale
import logging
import sys
from argparse import ArgumentParser, RawTextHelpFormatter
from argparse import OPTIONAL, ZERO_OR_MORE
from pprint import pformat
from textwrap import dedent

from hyper import HTTPConnection, HTTP20Connection
from hyper import __version__
from hyper.compat import is_py2, urlencode, urlsplit, write_to_stdout
from hyper.common.util import to_host_port_tuple


log = logging.getLogger('hyper')

PREFERRED_ENCODING = locale.getpreferredencoding()

# Various separators used in args
SEP_HEADERS = ':'
SEP_QUERY = '=='
SEP_DATA = '='

SEP_GROUP_ITEMS = [
    SEP_HEADERS,
    SEP_QUERY,
    SEP_DATA,
]


class KeyValue(object):
    """Base key-value pair parsed from CLI."""

    def __init__(self, key, value, sep, orig):
        self.key = key
        self.value = value
        self.sep = sep
        self.orig = orig


class KeyValueArgType(object):
    """A key-value pair argument type used with `argparse`.

    Parses a key-value arg and constructs a `KeyValue` instance.
    Used for headers, form data, and other key-value pair types.
    This class is inspired by httpie and implements simple tokenizer only.
    """
    def __init__(self, *separators):
        self.separators = separators

    def __call__(self, string):
        for sep in self.separators:
            splitted = string.split(sep, 1)
            if len(splitted) == 2:
                key, value = splitted
                return KeyValue(key, value, sep, string)


def make_positional_argument(parser):
    parser.add_argument(
        'method', metavar='METHOD', nargs=OPTIONAL, default='GET',
        help=dedent("""
        The HTTP method to be used for the request
        (GET, POST, PUT, DELETE, ...).
        """))
    parser.add_argument(
        '_url', metavar='URL',
        help=dedent("""
        The scheme defaults to 'https://' if the URL does not include one.
        """))
    parser.add_argument(
        'items',
        metavar='REQUEST_ITEM',
        nargs=ZERO_OR_MORE,
        type=KeyValueArgType(*SEP_GROUP_ITEMS),
        help=dedent("""
        Optional key-value pairs to be included in the request.
        The separator used determines the type:

        ':' HTTP headers:

            Referer:http://httpie.org  Cookie:foo=bar  User-Agent:bacon/1.0

        '==' URL parameters to be appended to the request URI:

            search==hyper

        '=' Data fields to be serialized into a JSON object:

            name=Hyper  language=Python  description='CLI HTTP client'
        """))


def make_troubleshooting_argument(parser):
    parser.add_argument(
        '--version', action='version', version=__version__,
        help='Show version and exit.')
    parser.add_argument(
        '--debug', action='store_true', default=False,
        help='Show debugging information (loglevel=DEBUG)')
    parser.add_argument(
        '--h2', action='store_true', default=False,
        help="Do HTTP/2 directly, skipping plaintext upgrade and ignoring "
             "NPN/ALPN."
    )


def split_host_and_port(hostname):
    if ':' in hostname:
        return to_host_port_tuple(hostname, default_port=443)
    return hostname, None


class UrlInfo(object):
    def __init__(self):
        self.fragment = None
        self.host = 'localhost'
        self.netloc = None
        self.path = '/'
        self.port = 443
        self.query = None
        self.scheme = 'https'
        self.secure = False


def set_url_info(args):
    info = UrlInfo()
    _result = urlsplit(args._url)
    for attr in vars(info).keys():
        value = getattr(_result, attr, None)
        if value:
            setattr(info, attr, value)

    if info.scheme == 'http' and not _result.port:
        info.port = 80

    # Set the secure arg is the scheme is HTTPS, otherwise do unsecured.
    info.secure = info.scheme == 'https'

    if info.netloc:
        hostname, _ = split_host_and_port(info.netloc)
        info.host = hostname  # ensure stripping port number
    else:
        if _result.path:
            _path = _result.path.split('/', 1)
            hostname, port = split_host_and_port(_path[0])
            info.host = hostname
            if info.path == _path[0]:
                info.path = '/'
            elif len(_path) == 2 and _path[1]:
                info.path = '/' + _path[1]
            if port is not None:
                info.port = port

    log.debug('Url Info: %s', vars(info))
    args.url = info


def set_request_data(args):
    body, headers, params = {}, {}, {}
    for i in args.items:
        if i.sep == SEP_HEADERS:
            if i.key:
                headers[i.key] = i.value
            else:
                # when overriding a HTTP/2 special header there will be a
                # leading colon, which tricks the command line parser into
                # thinking the header is empty
                k, v = i.value.split(':', 1)
                headers[':' + k] = v
        elif i.sep == SEP_QUERY:
            params[i.key] = i.value
        elif i.sep == SEP_DATA:
            value = i.value
            if is_py2:  # pragma: no cover
                value = value.decode(PREFERRED_ENCODING)
            body[i.key] = value

    if params:
        args.url.path += '?' + urlencode(params)

    if body:
        content_type = 'application/json'
        headers.setdefault('content-type', content_type)
        args.body = json.dumps(body)

    if args.method is None:
        args.method = 'POST' if args.body else 'GET'

    args.method = args.method.upper()
    args.headers = headers


def parse_argument(argv=None):
    parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
    parser.set_defaults(body=None, headers={})
    make_positional_argument(parser)
    make_troubleshooting_argument(parser)
    args = parser.parse_args(sys.argv[1:] if argv is None else argv)

    if args.debug:
        handler = logging.StreamHandler()
        handler.setLevel(logging.DEBUG)
        log.addHandler(handler)
        log.setLevel(logging.DEBUG)

    set_url_info(args)
    set_request_data(args)
    return args


def get_content_type_and_charset(response):
    charset = 'utf-8'
    content_type = response.headers.get('content-type')
    if content_type is None:
        return 'unknown', charset

    content_type = content_type[0].decode('utf-8').lower()
    type_and_charset = content_type.split(';', 1)
    ctype = type_and_charset[0].strip()
    if len(type_and_charset) == 2:
        charset = type_and_charset[1].strip().split('=')[1]

    return ctype, charset


def request(args):
    if not args.h2:
        conn = HTTPConnection(
            args.url.host, args.url.port, secure=args.url.secure
        )
    else:  # pragma: no cover
        conn = HTTP20Connection(
            args.url.host,
            args.url.port,
            secure=args.url.secure,
            force_proto='h2'
        )

    conn.request(args.method, args.url.path, args.body, args.headers)
    response = conn.get_response()
    log.debug('Response Headers:\n%s', pformat(response.headers))
    ctype, charset = get_content_type_and_charset(response)
    data = response.read()
    return data


def main(argv=None):
    args = parse_argument(argv)
    log.debug('Commandline Argument: %s', args)
    data = request(args)
    write_to_stdout(data)


if __name__ == '__main__':  # pragma: no cover
    main()
