# copyright 2015 CMCC
# All Rights Reserved

import hashlib

from trove.guestagent.strategies.storage import base
from trove.openstack.common import log as logging
from trove.common.i18n import _
from trove.common.remote import create_onest_client
from trove.common import cfg 

LOG = logging.getLogger(__name__)
CONF = cfg.CONF

CHUNK_SIZE = CONF.backup_chunk_size
MAX_FILE_SIZE = CONF.backup_segment_max_size
BACKUP_CONTAINER = CONF.backup_onest_container

class DownloadError(Exception):
    """Error running the oNest Download Command"""

class ONestDownloadIntegratyError(Exception):
    """Integrity error while running the ONest Download Command"""

class StreamReader(object):
    """Wrap the stream from the backup process and chunk it into segements."""

    def __init__(self, stream, filename, max_file_size=MAX_FILE_SIZE):
        self.stream = stream
        self.filename = filename
        self.container = BACKUP_CONTAINER
        self.max_file_size = max_file_size
        self.segment_length = 0
        self.process = None
        self.file_number = 0
        self.end_of_file = False
        self.end_of_segment = False
        self.segment_checksum = hashlib.md5()

    @property
    def base_filename(self):
        """Filename with extensions removed."""
        return self.filename.split('.')[0]

    @property
    def segment(self):
        return '%s_%08d' % (self.base_filename, self.file_number)

    @property
    def prefix(self):
        return '%s/%s_' % (self.container, self.base_filename)

    def read(self, chunk_size=CHUNK_SIZE):
        if self.end_of_segment:
            self.segment_length = 0
            self.segment_checksum = hashlib.md5()
            self.end_of_segment = False
 
        # Upload to a new file if we are starting or too large
        if self.segment_length > (self.max_file_size - chunk_size):
            self.file_number += 1
            self.end_of_segment = True
            return ''

        chunk = self.stream.read(chunk_size)
	if not chunk:
            self.end_of_file = True
            return ''

        self.segment_checksum.update(chunk)
        self.segment_length += len(chunk)
        return chunk


class ONestStorage(base.Storage):
    """Implementation of Storage Strategy for ONest."""
    __strategy_name__ = 'onest'

    def __init__(self, *args, **kwargs):
        super(ONestStorage, self).__init__(*args, **kwargs)
        self.connection = create_onest_client()


    def save(self, filename, stream):
        """Persist information from the local to onest.

        The backup file is saved to the location <BACKUP_CONTAINER>/<filename>.
        The filename is defined on the backup runner manifest property 
        which is typically in the format '<backup_id>.<ext>.gz' """

        # Create the container if it doesn't already exist
        containers_body = self.connection.list_buckets_of_user()
        containers = [item.container_name for item in containers_body.entries]
        if BACKUP_CONTAINER not in containers:
            self.connection.create_bucket(BACKUP_CONTAINER)

        # ONest Checksun
        onest_checksum = hashlib.md5()
        
        # Wrap the output of the backup process to segment it for swift
        #stream_reader = StreamReader(stream, filename)

        url = self.connection.get_location_without_bucket()
        # Full location where the backup manifest is stored.
        location = "%s/%s/%s" % (url, BACKUP_CONTAINER, filename)

        #while not stream_reader.end_of_file:
        #self.connection.put_object(BACKUP_CONTAINER, filename, stream.read())
        #    segment_checksum = stream_reader.segment_checksum.hexdigest()
        #    onest_checksum.update(segment_checksum)
        
        final_onest_checksum = onest_checksum.hexdigest()
        if not self.connection.put_object(BACKUP_CONTAINER, filename, stream.read()):
            LOG.error(_("Error saving data to oNest."))
            return False, "Error saving data to oNest!", None, location 
        return (True, "Successfully saved data to ONest!",
               final_onest_checksum , location)

    def _explodeLocation(self, location):
        storage_url = "/".join(location.split('/')[:-2])
        container = location.split('/')[-2]
        filename = location.split('/')[-1]
        return storage_url, container, filename

    def load(self, location, backup_checksum):
        "Restore a backup from the input stream to the restore_location"
        storage_url, container, filename = self._explodeLocation(location)
        info = self.connection.get_object_data(BACKUP_CONTAINER, filename)
        return info

    def _get_attr(self, original):
        """Get a friendly name from an object header key."""
        key = original.replace('-', '_')
        key = key.replace('x_cmcc_usermeta_', '')
        return key

    def _set_attr(self, original):
        """Return a onest friendly header key."""
        key = original.replace('_', '-')
        return 'x-cmcc-usermeta-%s' % key

    def load_metadata(self, location, backup_checksum):
        """Load metadata from oNest"""
        storage_url, container, filename = self._explodeLocation(location)
        headers = self.connection.get_object_meta(BACKUP_CONTAINER, filename)
        _meta = {}
        for key, value in headers.dict.iteritems():
            if key.startswith('x-cmcc-usermeta-'):
                _meta[self._get_attr(key)] = value
        return _meta


    def save_metadata(self, location, metadata={}):
        """Save metadata to a swfit objcet."""
        storage_url, container, filename = self._explodeLocation(location)
        headers = {}
        for key, value in metadata.iteritems():
            headers[self._set_attr(key)] = value
        LOG.info(_("Writing metadata: %s"), str(headers))
        self.connection.put_object_meta(BACKUP_CONTAINER, filename, headers)
