# -*- coding: utf-8 -*-
import collections
import datetime
import fnmatch
import json
import logging
import os

from botocore.exceptions import ClientError

from s3backup.clients import SyncClient, SyncObject


logger = logging.getLogger('s3backup')


S3Uri = collections.namedtuple('S3Uri', ['bucket', 'key'])


def parse_s3_uri(uri):
    tokens = uri.replace('s3://', '').split('/')
    if len(tokens) < 2:
        return None

    bucket = tokens[0]
    key = '/'.join(tokens[1:])
    return S3Uri(bucket, key)


def to_timestamp(dt):
    epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
    return (dt - epoch) / datetime.timedelta(seconds=1)


class S3SyncClient(SyncClient):
    def __init__(self, client, bucket, prefix, ignore_files=None):
        self.client = client
        self.bucket = bucket
        self.prefix = prefix
        self.index = self.load_index()
        self.ignore_files = ['.index']
        if ignore_files is not None:
            self.ignore_files.extend(ignore_files)

    def __repr__(self):
        return 'S3SyncClient<{}, {}>'.format(self.bucket, self.prefix)

    def get_uri(self):
        return 's3://{}/{}'.format(self.bucket, self.prefix)

    def index_path(self):
        return os.path.join(self.prefix, '.index')

    def put(self, key, sync_object, callback=None):
        self.client.upload_fileobj(
            Bucket=self.bucket,
            Key=os.path.join(self.prefix, key),
            Fileobj=sync_object.fp,
            Callback=callback,
        )
        self.set_remote_timestamp(key, sync_object.timestamp)

    def get(self, key):
        resp = self.client.get_object(
            Bucket=self.bucket,
            Key=os.path.join(self.prefix, key),
        )
        return SyncObject(resp['Body'], resp['ContentLength'], to_timestamp(resp['LastModified']))

    def delete(self, key):
        resp = self.client.delete_objects(
            Bucket=self.bucket,
            Delete={
                'Objects': [{'Key': os.path.join(self.prefix, key)}]
            }
        )
        return 'Deleted' in resp

    def load_index(self):
        try:
            resp = self.client.get_object(
                Bucket=self.bucket,
                Key=self.index_path(),
            )
            data = json.loads(resp['Body'].read().decode('utf-8'))
            return data
        except (ClientError, ValueError):
            return {}

    def flush_index(self):
        self.client.put_object(
            Bucket=self.bucket,
            Key=self.index_path(),
            Body=json.dumps(self.index),
        )

    def get_local_keys(self):
        results = []
        resp = self.client.list_objects_v2(
            Bucket=self.bucket,
            Prefix=self.prefix,
        )
        if 'Contents' not in resp:
            return results

        for obj in resp['Contents']:
            key = os.path.relpath(obj['Key'], self.prefix)
            if not any(fnmatch.fnmatch(key, pattern) for pattern in self.ignore_files):
                results.append(key)
            else:
                logger.debug('Ignoring %s', key)

        return results

    def get_real_local_timestamp(self, key):
        try:
            response = self.client.head_object(
                Bucket=self.bucket,
                Key=os.path.join(self.prefix, key),
            )
            return to_timestamp(response['LastModified'])
        except ClientError:
            return None

    def get_index_keys(self):
        return self.index.keys()

    def get_index_local_timestamp(self, key):
        return self.index.get(key, {}).get('local_timestamp')

    def set_index_local_timestamp(self, key, timestamp):
        if key not in self.index:
            self.index[key] = {}
        self.index[key]['local_timestamp'] = timestamp

    def get_remote_timestamp(self, key):
        return self.index.get(key, {}).get('remote_timestamp')

    def set_remote_timestamp(self, key, timestamp):
        if key not in self.index:
            self.index[key] = {}
        self.index[key]['remote_timestamp'] = timestamp

@@ -103,16 +103,28 @@
 mport os
+%0Aimport zlib
 %0A%0Afrom b
@@ -162,16 +162,30 @@
 tError%0A%0A
+import magic%0A%0A
 from s3b
@@ -2297,86 +2297,668 @@
     
-data = json.loads(resp%5B'Body'%5D.read().decode('utf-8'))%0A            return data
+body = resp%5B'Body'%5D.read()%0A            content_type = magic.from_buffer(body, mime=True)%0A            if content_type == 'text/plain':%0A                logger.debug('Detected plain text encoding for index')%0A                return json.loads(body.decode('utf-8'))%0A            elif content_type == 'application/zlib':%0A                logger.debug('Detected zlib encoding for index')%0A                body = zlib.decompress(body)%0A                return json.loads(body.decode('utf-8'))%0A            elif content_type == 'application/x-empty':%0A                return %7B%7D%0A            else:%0A                raise ValueError('Unknown content type for index', content_type)
 %0A   
@@ -2985,20 +2985,8 @@
 rror
-, ValueError
 ):%0A 
@@ -3023,34 +3023,320 @@
 flush_index(self
-):
+, compressed=True):%0A        data = json.dumps(self.index).encode('utf-8')%0A        if compressed:%0A            logger.debug('Using zlib encoding for writing index')%0A            data = zlib.compress(data)%0A        else:%0A            logger.debug('Using plain text encoding for writing index')%0A
 %0A        self.cl
@@ -3436,38 +3436,20 @@
    Body=
-json.dumps(self.index)
+data
 ,%0A      

Support zlib encoding for s3 indexes