# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for
# license information.
# -------------------------------------------------------------------------
from flask import (
    request,
    jsonify as flask_jsonify,
)
from urllib.parse import urlparse, urlunparse
from .structures import CaseInsensitiveDict
import json

ENV_HEADERS = (
    "X-Varnish",
    "X-Request-Start",
    "X-Heroku-Queue-Depth",
    "X-Real-Ip",
    "X-Forwarded-Proto",
    "X-Forwarded-Protocol",
    "X-Forwarded-Ssl",
    "X-Heroku-Queue-Wait-Time",
    "X-Forwarded-For",
    "X-Heroku-Dynos-In-Use",
    "X-Forwarded-Protocol",
    "X-Forwarded-Port",
    "X-Request-Id",
    "Via",
    "Total-Route-Time",
    "Connect-Time",
)


def assert_with_message(param_name, expected_value, actual_value):
    assert expected_value == actual_value, "Expected '{}' to be '{}', got '{}'".format(
        param_name, expected_value, actual_value
    )


def jsonify(*args, **kwargs):
    response = flask_jsonify(*args, **kwargs)
    if not response.data.endswith(b"\n"):
        response.data += b"\n"
    return response


def get_url(request):
    """
    Since we might be hosted behind a proxy, we need to check the
    X-Forwarded-Proto, X-Forwarded-Protocol, or X-Forwarded-SSL headers
    to find out what protocol was used to access us.
    """
    protocol = request.headers.get("X-Forwarded-Proto") or request.headers.get("X-Forwarded-Protocol")
    if protocol is None and request.headers.get("X-Forwarded-Ssl") == "on":
        protocol = "https"
    if protocol is None:
        return request.url
    url = list(urlparse(request.url))
    url[0] = protocol
    return urlunparse(url)


def get_files():
    """Returns files dict from request context."""

    files = {}

    for k, v in request.files.items():
        content_type = request.files[k].content_type or "application/octet-stream"
        val = json_safe(v.read(), content_type)
        if files.get(k):
            if not isinstance(files[k], list):
                files[k] = [files[k]]
            files[k].append(val)
        else:
            files[k] = val

    return files


def get_headers(hide_env=True):
    """Returns headers dict from request context."""

    headers = dict(request.headers.items())

    if hide_env and ("show_env" not in request.args):
        for key in ENV_HEADERS:
            try:
                del headers[key]
            except KeyError:
                pass

    return CaseInsensitiveDict(headers.items())


def semiflatten(multi):
    """Convert a MultiDict into a regular dict. If there are more than one value
    for a key, the result will have a list of values for the key. Otherwise it
    will have the plain value."""
    if multi:
        result = multi.to_dict(flat=False)
        for k, v in result.items():
            if len(v) == 1:
                result[k] = v[0]
        return result
    else:
        return multi


def json_safe(string, content_type="application/octet-stream"):
    """Returns JSON-safe version of `string`.

    If `string` is a Unicode string or a valid UTF-8, it is returned unmodified,
    as it can safely be encoded to JSON string.

    If `string` contains raw/binary data, it is Base64-encoded, formatted and
    returned according to "data" URL scheme (RFC2397). Since JSON is not
    suitable for binary data, some additional encoding was necessary; "data"
    URL scheme was chosen for its simplicity.
    """
    try:
        string = string.decode("utf-8")
        json.dumps(string)
        return string
    except (ValueError, TypeError):
        return b"".join([b"data:", content_type.encode("utf-8"), b";base64,", base64.b64encode(string)]).decode("utf-8")


def get_dict(*keys, **extras):
    """Returns request dict of given keys."""

    _keys = ("url", "args", "form", "data", "origin", "headers", "files", "json", "method")

    assert all(map(_keys.__contains__, keys))
    data = request.data
    form = semiflatten(request.form)

    try:
        _json = json.loads(data.decode("utf-8"))
    except (ValueError, TypeError):
        _json = None

    d = dict(
        url=get_url(request),
        args=semiflatten(request.args),
        form=form,
        data=json_safe(data),
        origin=request.headers.get("X-Forwarded-For", request.remote_addr),
        headers=get_headers(),
        files=get_files(),
        json=_json,
        method=request.method,
    )

    out_d = {}

    for key in keys:
        out_d[key] = d.get(key)

    out_d.update(extras)

    return out_d


def get_base_url(request):
    return "http://" + request.host


__all__ = ["assert_with_message", "get_dict", "jsonify"]
