import os
import string
import time

from parallels.core.dump.file_adapters import TarFileAdapter
from parallels.core.dump.file_adapters import ZipFileAdapter
from parallels.core.utils.common import cached
from plesk_migrator.compatibility.utils import stringio


class DumpArchiveWriter(object):
    """
    :type _archive_file:
    :type _archive_adapter: parallels.core.dump.file_adapters.ArchiveAdapter
    :type _timestamp: str | unicode
    :type _entities_count: int
    """

    MAX_CLIENT_NAME_LENGTH = 25
    MAX_DOMAIN_NAME_LENGTH = 47

    def __init__(self, archive_path):
        """
        :type archive_path: str | unicode
        """
        self._archive_file = open(archive_path, 'wb')
        if archive_path.endswith('.tar'):
            self._archive_adapter = TarFileAdapter(fileobj=self._archive_file, mode='w')
        else:
            self._archive_adapter = ZipFileAdapter(fileobj=self._archive_file, mode='w')
        self._timestamp = time.strftime('%y%m%d%H%M')
        self._entities_count = 0

    def close(self):
        self._archive_adapter.close()
        self._archive_file.close()

    def add_dump(self, contents, domain_name=None, client_name=None, reseller_name=None, guid=None):
        """
        :type contents: str | unicode
        :type reseller_name: str | unicode | None
        :type client_name: str | unicode | None
        :type domain_name: str | unicode | None
        :type guid: str | unicode | None
        """
        object_id = None
        dump_dir_path = []
        if reseller_name:
            object_id = self._get_object_id('reseller', reseller_name, self.MAX_CLIENT_NAME_LENGTH)
            dump_dir_path += ['resellers', object_id]
        if client_name:
            object_id = self._get_object_id('client', client_name, self.MAX_CLIENT_NAME_LENGTH)
            dump_dir_path += ['clients', object_id]
        if domain_name:
            object_id = self._get_object_id('domain', domain_name.encode('idna'), self.MAX_DOMAIN_NAME_LENGTH)
            dump_dir_path += ['domains', object_id]
        props = ''
        if guid and object_id:
            self._archive_adapter.add_file(os.path.join(
                '.discovered', '%s_%s' % (guid.encode('utf-8'), object_id)
            ), 0, stringio('', is_unicode=False))
            props += 'GUID_%s\nobjectid_%s' % (guid.encode('utf-8'), object_id)
        props_file_path = dump_dir_path + ['.discovered', 'info_%s' % self._timestamp, 'props']
        self._archive_adapter.add_file(os.path.join(*props_file_path), len(props), stringio(props, is_unicode=False))
        dump_file_path = dump_dir_path + ['info_%s.xml' % self._timestamp]
        self._archive_adapter.add_file(
            os.path.join(*dump_file_path), len(contents), stringio(contents, is_unicode=False)
        )

    def _get_object_id(self, entity_type, entity_name, max_length):
        """Replace special symbols and truncate entity name to form valid path on filesystem.

        For example, there is limit on max path lenght on Windwos in ~250 symbols and we should truncate path
        not to exceed this limit.

        :type entity_type: str | unicode
        :type entity_name: str | unicode
        :type max_length: int
        :rtype: str | unicode
        """
        object_id = entity_name.encode('utf-8').translate(string.maketrans(r'\/:*?"><|&! ', '------------'))
        if len(object_id) > max_length or object_id != entity_name:
            entity_id = self._get_entity_id(entity_type, entity_name)
            object_id = object_id[:max_length - len(entity_id) - 1] + '_' + entity_id
        return object_id

    # noinspection PyUnusedLocal
    @cached
    def _get_entity_id(self, entity_type, entity_name):
        """
        :type entity_type: str | unicode
        :type entity_name: str | unicode
        :rtype: str | unicode
        """
        self._entities_count += 1
        return str(self._entities_count)
