import base64
import hmac
import urllib.parse as parse
from email.utils import formatdate
from hashlib import sha1 as sha

qsa_of_interest = {
    "acl",
    "cors",
    "defaultObjectAcl",
    "location",
    "logging",
    "partNumber",
    "policy",
    "requestPayment",
    "torrent",
    "versioning",
    "versionId",
    "versions",
    "website",
    "uploads",
    "uploadId",
    "response-content-type",
    "response-content-language",
    "response-expires",
    "response-cache-control",
    "response-content-disposition",
    "response-content-encoding",
    "delete",
    "lifecycle",
    "tagging",
    "restore",
    "notification",
    "thumbnail",
    "queryadp",
    "adp",
    "asyntask",
    "querytask",
    "domain",
    "storageClass",
    "websiteConfig",
    "compose",
    "quota",
    "policy",
    "crr",
    "fetch",
    "append",
    "position",
    "mirror",
    "retention",
    "recycle",
    "recover",
    "clear",
    "inventory",
    "id",
}


def url_encode(key):
    if not key:
        return ""
    return parse.quote(str(key), safe="/~")


def encode_params(query_args):
    if not query_args:
        return ""
    if isinstance(query_args, dict):
        map_args = {k: v for k, v in query_args.items() if k}
    else:
        map_args = {}
        for param in filter(None, query_args.split("&")):
            k, _, v = param.partition("=")
            if k:
                map_args[k] = v
    buf_list = []
    for k in sorted(map_args):
        if k not in qsa_of_interest:
            continue
        v = map_args[k]
        if v is None or v == "":
            buf_list.append(k)
        else:
            buf_list.append(f"{k}={v}")
    return "&".join(buf_list)


def canonical_resource(bucket, key, query_args):
    buf = f"/{bucket}/" if bucket else "/"
    if key:
        buf += url_encode(key)

    buf = buf.replace("//", "/%2F")
    params = encode_params(query_args)
    return f"{buf}?{params}" if params else buf


def canonical_headers(headers):
    if not headers:
        return ""
    interesting_headers = {
        key.lower(): value
        for key, value in headers.items()
        if key.lower().startswith("x-kss-")
    }
    return "\n".join(
        f"{header_key}:{interesting_headers[header_key]}"
        for header_key in sorted(interesting_headers)
    )


def canonical_string(
    method, bucket="", key="", query_args=None, headers=None, expires=None
):
    headers = headers or {}
    query_args = query_args or ""

    can_resource = canonical_resource(bucket, key, query_args)
    can_headers = canonical_headers(headers)
    content_md5 = ""
    content_type = ""
    date = ""
    for header_key, val in headers.items():
        lk = header_key.lower()
        if not val:
            continue
        if lk == "content-md5":
            content_md5 = val
        elif lk == "content-type":
            content_type = val
        elif lk == "date":
            date = val
    if expires:
        date = str(expires)
    sign_list = [method, content_md5, content_type, date]
    if can_headers:
        sign_list.append(can_headers)
    sign_list.append(can_resource)
    sign_str = "\n".join(sign_list)
    print(f"sign_list: {sign_list}")
    return sign_str


def encode(secret_access_key, str_to_encode, urlencode=False):
    secret_access_key = secret_access_key.encode("utf-8")
    str_to_encode = str_to_encode.encode("utf-8")
    digest = hmac.new(secret_access_key, str_to_encode, sha).digest()
    b64_hmac = base64.b64encode(digest).decode("utf-8").strip()
    if urlencode:
        return parse.quote_plus(b64_hmac)
    return b64_hmac


def add_auth_header(
    access_key_id, secret_access_key, headers, method, bucket, key, query_args
):
    if not access_key_id:
        return
    if "Date" not in headers:
        headers["Date"] = formatdate(usegmt=True)

    c_string = canonical_string(method, bucket, key, query_args, headers)
    headers["Authorization"] = (
        f"KSS {access_key_id}:{encode(secret_access_key, c_string)}"
    )
