import os
import re
from collections import defaultdict
from xml.etree import ElementTree

from plesk_migrator.compatibility.utils import stringio


class BaseDump(object):
    def __init__(self, path, backup_prefix=None, backup_id=None):
        self._path = path
        self._backup_prefix = backup_prefix
        self._backup_id = backup_id
        self._indexed_entities = None

    def get_entities(self):
        """
        Returns a list of tuples: dump_xml, reseller_name, client_name, domain_name, guid
        :rtype: list[tuple[str|unicode, str|unicode|None, str|unicode|None, str|unicode|None, str|unicode|None]]
        """
        entities = []
        for entity_name in self.list_xmls():
            dump_xml = self._get_content(entity_name)
            reseller_name, client_name, domain_name = None, None, None
            dump = ElementTree.parse(stringio(dump_xml, is_unicode=False))
            reseller_node, client_node, domain_node, guid = dump.find('reseller'), None, None, None
            if reseller_node is not None:
                reseller_name, client_name, domain_name = reseller_node.get('name'), None, None
                guid = reseller_node.get('guid')
            else:
                if re.search('^resellers/', entity_name) is None:
                    reseller_name = None
                client_node = dump.find('client')
            if client_node is not None:
                client_name, domain_name = client_node.get('name'), None
                guid = client_node.get('guid')
            else:
                if re.search('^(resellers/[^/]+/)?clients/', entity_name) is None:
                    client_name = None
                domain_node = dump.find('domain')
            if domain_node is not None:
                domain_name = domain_node.get('name')
                guid = domain_node.get('guid')
            else:
                domain_name = None
            entities.append((dump_xml, reseller_name, client_name, domain_name, guid))
        return entities

    def get_entity_content(self, entity_type, entity_name):
        """
        :type entity_type: str|unicode
        :type entity_name: str|unicode
        :rtype: str|unicode|None
        """
        if self._indexed_entities is None:
            self._index_entities()
        entities_by_type = self._indexed_entities.get(entity_type)
        if entities_by_type is not None:
            entity = entities_by_type.get(entity_name)
            if entity is not None:
                return self._get_content(entity)
        return None

    def list_xmls(self):
        """
        :return: [str|unicode]
        """
        entry_names = []
        for entry_name in self._list_files():
            if self._is_xml_file(entry_name):
                entry_names.append(entry_name)
        return entry_names

    def _index_entities(self):
        single_domain_dump = True
        self._indexed_entities = defaultdict(dict)
        resellers_dump_dir = 'resellers'
        if self._path_exists(resellers_dump_dir):
            single_domain_dump = False
            self._index_resellers(resellers_dump_dir)
        clients_dump_dir = 'clients'
        if self._path_exists(clients_dump_dir):
            single_domain_dump = False
            self._index_clients(clients_dump_dir)
        domains_dump_dir = 'domains'
        if self._path_exists(domains_dump_dir):
            single_domain_dump = False
            self._index_domains(domains_dump_dir)
        if single_domain_dump:
            self._index_domain('.')

    def _index_resellers(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                self._index_reseller(dir_entry_path)

    def _index_reseller(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                if dir_entry == 'clients':
                    self._index_clients(dir_entry_path)
                elif dir_entry == 'domains':
                    self._index_domains(dir_entry_path)
                continue
            if self._is_dump_file(dir_entry):
                self._index_entity('reseller', dir_entry_path)

    def _index_clients(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                self._index_client(dir_entry_path)

    def _index_client(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                if dir_entry == 'domains':
                    self._index_domains(dir_entry_path)
                continue
            if self._is_dump_file(dir_entry):
                self._index_entity('client', dir_entry_path)

    def _index_domains(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                self._index_domain(dir_entry_path)

    def _index_domain(self, dump_dir):
        """
        :type dump_dir: str|unicode
        """
        for dir_entry in self._list_dir(dump_dir):
            dir_entry_path = self._join_path_remove_left_dot(dump_dir, dir_entry)
            if self._is_dir(dir_entry_path):
                continue
            if self._is_dump_file(dir_entry):
                self._index_entity('domain', dir_entry_path)

    def _index_entity(self, entity_type, entity_dump_path):
        """
        :type entity_type: str|unicode
        :type entity_dump_path: str|unicode
        """
        entity_dump_content = self._get_content(entity_dump_path)
        entity_node = ElementTree.parse(stringio(entity_dump_content, is_unicode=False)).find(entity_type)
        if entity_node is None:
            return
        entity_name = entity_node.get('name')
        if entity_name:
            self._indexed_entities[entity_type][entity_name] = entity_dump_path

    @staticmethod
    def _is_xml_file(dump_file):
        """
        :type dump_file: str|unicode
        :return: bool
        """
        return dump_file.endswith('.xml')

    def _join_path_remove_left_dot(self, path1, path2):
        """
        :type path1: str|unicode
        :type path2: str|unicode
        :return: str|unicode
        """
        path = self._join_path(path1, path2)
        if path.startswith('.' + self._path_sep()):
            return path[2:]
        return path

    def _is_dump_file(self, dump_file):
        """
        :type dump_file: str|unicode
        :return: bool
        """
        dump_file_name = os.path.basename(dump_file)
        match = re.search(r'^([^_]+).*_(\d+)\.xml$', dump_file_name)
        if match is None:
            return False
        if (
                (self._backup_prefix is not None and self._backup_prefix != match.group(1))
                or (self._backup_id is not None and self._backup_id != match.group(2))
        ):
            return False
        return True

    def _list_files(self):
        """
        :return: [str|unicode]
        """
        raise NotImplementedError()

    def _get_content(self, name):
        """
        :type name: str|unicode
        :return: str|unicode
        """
        raise NotImplementedError()

    def _path_exists(self, path):
        """
        :type path: str|unicode
        :return: bool
        """
        raise NotImplementedError()

    def _list_dir(self, path):
        """
        :type path: str|unicode
        :return: [str|unicode]
        """
        raise NotImplementedError()

    def _is_dir(self, path):
        """
        :type path: str|unicode
        :return: bool
        """
        raise NotImplementedError()

    def _join_path(self, path1, path2):
        """
        :type path1: str|unicode
        :type path2: str|unicode
        :return: str|unicode
        """
        raise NotImplementedError()

    def _path_sep(self):
        """
        :return: str|unicode
        """
        raise NotImplementedError()
