import sys
import json
import base64

from collections import namedtuple
from os.path import basename

from .common import _content_data
from .utils import MirrorException, fp_or_bytes


S3Api = namedtuple("S3Api", ["session", "client", "base"])


def _status(response):
    return response.get("ResponseMetadata", {}).get("HTTPStatusCode", 0)


def get_repo_api(config):
    try:
        from botocore import session as b_session
    except ImportError:
        raise MirrorException("Please install the 'botocore' package.")
    session = b_session.get_session()
    client = session.create_client("s3")
    base = config.dest_site + "/" if config.dest_site else ""
    return S3Api(session, client, base)


def _join(api, *args):
    uri = api.base + "/".join(a for a in args if a)
    if uri.startswith("s3://"):
        uri = uri[5:]
    if "/" in uri:
        bucket, key = uri.split("/", 1)
    else:
        bucket, key = uri, ""
    return {"Bucket": bucket, "Key": key}


def _exc(exc):
    return "%s: %s" % (type(exc).__name__, exc)


def list_objects(api, cname, platform=None, repodata=None):
    kwargs = _join(api, cname, platform)
    kwargs = {
        "Bucket": kwargs["Bucket"],
        "MaxKeys": 1000,
        "Prefix": kwargs["Key"] + "/",
        "Delimiter": "/",
    }
    packages = {}
    while True:
        response = api.client.list_objects_v2(**kwargs)
        contents = response.get("Contents", [])
        for frec in contents:
            packages[basename(frec["Key"])] = {
                "size": frec["Size"],
                "md5": frec["ETag"].strip('"'),
            }
        if len(contents) < 1000:
            break
        kwargs["StartAfter"] = contents[-1]["Key"]
    return packages


def get_object(api, *path):
    kwargs = _join(api, *path)
    try:
        response = api.client.get_object(**kwargs)
        return json.loads(response["Body"].read())
    except api.client.exceptions.NoSuchKey:
        pass


def del_object(api, *path, metadata=None):
    kwargs = _join(api, *path)
    response = api.client.delete_object(**kwargs)
    if _status(response) not in (200, 204, 404):
        raise MirrorException(str(response))


def put_object(api, *path, path_or_data=None, metadata=None):
    kwargs = _join(api, *path)
    if metadata:
        kwargs["ContentLength"] = metadata["size"]
        sha256 = base64.b64encode(bytes.fromhex(metadata["sha256"])).decode("ascii")
        kwargs["ChecksumSHA256"] = sha256
    content_data = _content_data(path[-1])
    kwargs.update((k.replace("-", ""), v) for k, v in content_data.items())
    with fp_or_bytes(path_or_data) as fp:
        response = api.client.put_object(Body=fp, **kwargs)
    if _status(response) != 200:
        raise MirrorException(str(response))


def main():
    from .common import main as _main

    _main(sys.modules[__name__])
