import sys

from parallels.core.logging import get_logger
from parallels.core.utils.migrator_utils import get_optional_option_value
from parallels.plesk.source.web import messages
from parallels.plesk.source.web.actions.base.site_migration import SiteMigrationAction
from parallels.plesk.source.web.utils.list_files.factory import create_list_files

logger = get_logger(__name__)


class RetrieveFilesTree(SiteMigrationAction):
    """Retrieve tree of files on the server and cache it to persistent storage, to improve performance"""

    def get_description(self):
        """Get short description of action as string

        :rtype: str | unicode
        """
        return messages.ACTION_RETRIEVE_FILES_TREE_DESCRIPTION

    def get_failure_message(self, global_context):
        """Get message for situation when action failed

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :rtype: str | unicode
        """
        return messages.ACTION_RETRIEVE_FILES_TREE_FAILURE

    def run(self, global_context):
        """
        :type global_context: parallels.plesk.source.web.global_context.WebGlobalMigrationContext
        """
        if (
            not get_optional_option_value(global_context.options, 'reload_source_data', False) and
            global_context.site_info_storage.has_files()
        ):
            logger.finfo(messages.ACTION_RETRIEVE_FILES_TREE_SOURCE_CACHED)
            return

        logger.finfo(messages.ACTION_RETRIEVE_FILES_TREE_SOURCE)

        source = global_context.conn.get_source()

        absolute_files_root = source.get_absolute_files_root()
        absolute_filesystem_root_path = source.get_absolute_filesystem_root_path()

        list_files = create_list_files(source)

        files_tree = list_files.list_files(source)
        encoding = list_files.get_encoding(source)

        files_tree = self._cleanup_directory_tree(global_context, encoding, files_tree)
        global_context.site_info_storage.set_files(files_tree)
        global_context.status_storage.update_files_list(absolute_files_root, files_tree)

        global_context.site_info_storage.set_root_path(absolute_files_root)
        global_context.site_info_storage.set_filesystem_root_path(absolute_filesystem_root_path)

    @classmethod
    def _cleanup_directory_tree(cls, global_context, encoding, tree_items):
        """Remove directories related to RPC agent and perform unicode conversion to display in GUI and save to JSON.
        
        Actually a filename could be a binary string, and for example on Linux in most situations it
        could contain practically any sequence of bytes.
        But:
        1) Browsers work with unicode and could display correctly unicode symbols,
        not binary strings in unknown encoding.
        2) JSON could store only unicode symbols.

        So, we try to decode file names here, and only then put them to JSON.
        This causes loss of some symbols which can not be decoded in that encoding.

        :type global_context: parallels.plesk.source.web.global_context.WebGlobalMigrationContext
        :type encoding: str
        :type tree_items: list 
        :rtype: list 
        """
        if sys.version_info.major >= 3:
            str_type = str
        else:
            str_type = unicode

        result = []

        agent_id = global_context.conn.get_source().get_plesk_agent_id()

        if agent_id is None:
            return tree_items

        for tree_item in tree_items:
            if isinstance(tree_item, dict):
                result_tree_item = {}
                for dirname, subitems in tree_item.items():
                    if agent_id not in dirname:
                        if not isinstance(dirname, str_type):
                            dirname = dirname.decode(encoding, errors='replace')
                        result_tree_item[dirname] = cls._cleanup_directory_tree(global_context, encoding, subitems)
                if len(result_tree_item) > 0:
                    result.append(result_tree_item)
            else:
                if not isinstance(tree_item, str_type):
                    tree_item = tree_item.decode(encoding, errors='replace')
                result.append(tree_item)

        return result
