import os
import json
import threading

import boto3
from boto3.s3.transfer import S3Transfer

from elrond.crypto import get_file_digest

DEFAULT_CHUNK_SIZE = 64 * 1024 * 1024

ELROND_S3_SINGLETON_CLIENT = None
ELROND_S3_SUPPORTED_REGIONS = [
    'EU',
    'eu-west-1',
    'us-west-1',
    'us-west-2',
    'ap-south-1',
    'ap-southeast-1',
    'ap-southeast-2',
    'ap-northeast-1',
    'sa-east-1',
    'cn-north-1',
    'eu-central-1'
]

def analyse(file_path):
    res_st = os.stat(file_path, follow_symlinks=True)
    return {
        'mode': res_st.st_mode,
        'atime': res_st.st_atime,
        'mtime': res_st.st_mtime,
        'ctime': res_st.st_ctime,
        'size': res_st.st_size,
        'digest': {
            'algorithm': 'sha256',
            'value': get_file_digest(file_path)
        }
    }

def update_metadata(file_path, metadata):
    os.chmod(file_path, metadata['mode'])
    os.utime(file_path, (metadata['atime'], metadata['mtime']))

def metadata2str(metadata):
    return json.dumps(metadata,separators=(',', ':'))

def str2metadata(metadata_str):
    return json.loads(metadata_str)

def chunk(stream, chuck_size=DEFAULT_CHUNK_SIZE):
    for block in iter(lambda:stream.read(chuck_size),b''):
        yield block

def get_client():
    global ELROND_S3_SINGLETON_CLIENT
    if ELROND_S3_SINGLETON_CLIENT is None:
        ELROND_S3_SINGLETON_CLIENT = boto3.client('s3')
    return ELROND_S3_SINGLETON_CLIENT

def get_buckets():
    client = get_client()
    return [bucket['Name'] for bucket in client.list_buckets()['Buckets']]

def get_bucket(bucket_name):
    client = get_client()
    if bucket_name in get_buckets():
        location = client.get_bucket_location(
            Bucket=bucket_name
        )['LocationConstraint']
        return (bucket_name, location)
    else:
        location = os.environ['AWS_DEFAULT_REGION']
        assert location in ELROND_S3_SUPPORTED_REGIONS
        res = client.create_bucket(
            Bucket=bucket_name,
            CreateBucketConfiguration={
                'LocationConstraint': location
            }
        )
        return (bucket_name, location)


class ProgressPercentage(object):
    def __init__(self, filename):
        self._filename = filename
        self._size = float(os.path.getsize(filename))
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        # To simplify we'll assume this is hooked up
        # to a single filename.
        with self._lock:
            self._seen_so_far += bytes_amount
            percentage = (self._seen_so_far / self._size) * 100
            sys.stdout.write(
                "\\r%s  %s / %s  (%.2f%%)" % (
                    self._filename, self._seen_so_far, self._size,
                    percentage))
            sys.stdout.flush()


def upload(bucket_name, file_path, key_name):
    file_metadata = analyse(file_path)
    if file_metadata['size'] > 100 * 1024 * 1024:
        multipart_mode = True

    client = get_client()
    if multipart_mode:
        pass
    else:
        transfer = S3Transfer(client)
        transfer.upload_file(
            file_path, bucket_name, key_name,
            extra_args={
                'ACL': 'private',
                'Metadata': metadata2str(file_metadata),
                'ContentType': 'application/octet-stream'
            },
            callback=ProgressPercentage(file_path))
