from collections import OrderedDict

import requests
import six
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from tqdm import tqdm


class ProgressBar(tqdm):

    def update_to(self, n):
        """
        identical to update, except `n` should be current value and not delta.
        """
        self.update(n - self.n)


PRESERVED_ORDER_V1 = ("policy", "AWSAccessKeyId", "key", "signature")
PRESERVED_SIGNATURE_V4 = (
    "key", "success_action_status",
    "policy", "x-amz-credential", "x-amz-algorithm",
    "x-amz-date", "x-amz-signature"
)


def _order_upload_keys(fields, keys):
    output_fields = OrderedDict()
    for key in keys:
        if fields.get(key):
            output_fields[key] = fields[key]
    return output_fields


def format_request_body(fields):
    if set(fields.keys()).issubset(set(PRESERVED_ORDER_V1)):
        return _order_upload_keys(fields, PRESERVED_ORDER_V1)
    elif set(fields.keys()).issubset(set(PRESERVED_SIGNATURE_V4)):
        return _order_upload_keys(fields, PRESERVED_SIGNATURE_V4)
    else:
        return fields


def upload(destination, file, progress_bar=False):
    url = destination["url"]
    fields = format_request_body(destination['fields'])

    fields["file"] = ("features_backup.rcdb", file)
    encoder = MultipartEncoder(fields=fields)
    if not progress_bar:
        return requests.post(
            url, data=encoder, headers={"Content-Type": encoder.content_type}
        )

    with ProgressBar(total=encoder.len, unit="bytes", unit_scale=True, leave=False) as bar:
        monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update_to(monitor.bytes_read))
        return requests.post(
            url, data=monitor, headers={"Content-Type": encoder.content_type}
        )


def download(source, destination=None):
    is_file = False
    if destination is None:
        fp = six.BytesIO()
    elif isinstance(destination, six.BytesIO) or isinstance(destination, six.StringIO):
        fp = destination
    else:
        fp = open(destination, 'wb')
        is_file = True

    r = requests.get(source, stream=True)
    file_size = int(r.headers['Content-Length'])
    chunk, chunk_size = 1, 1024
    with ProgressBar(total=file_size, unit='bytes', unit_scale=True, leave=False) as bar:
        for chunk in ProgressBar(
            r.iter_content(chunk_size)
        ):
            fp.write(chunk)

    if is_file:
        fp.close()
        return destination
    return fp
