import sys

from parallels.core import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.plesk.source.web import messages
from parallels.core.utils.common import is_ascii_string
from parallels.plesk.source.web.utils.list_files.base import ListFilesBase

logger = get_logger(__name__)


class ListFilesHybrid(ListFilesBase):
    def list_files(self, source):
        """
        :type source: parallels.plesk.source.web.connections.WebSource
        :rtype: list
        """
        try:
            web_rpc_runner = source.get_web_rpc_runner()
            files_tree = web_rpc_runner.get_dir_tree(source.get_absolute_files_root())
        except Exception as e:
            with logger.info_level_context(
                messages.FAILED_TO_LIST_FILES_WITH_WEB_RPC_AGENT_FALLBACK,
                reason=e, transport=source.get_filesystem_runner_title()
            ):
                logger.exception()

            with source.filesystem_runner() as runner:
                return runner.get_dir_tree(source.get_files_root())
        else:
            if len(files_tree) > 0:
                return self._fetch_files_and_dirs_with_suspicious_symbols_with_filesystem_runner(source, files_tree)
            else:
                logger.fdebug(
                    messages.EMPTY_FILE_LIST_WEB_RPC_AGENT_FALLBACK,
                    transport=source.get_filesystem_runner_title()
                )
                with source.filesystem_runner() as runner:
                    return runner.get_dir_tree(source.get_files_root())

    def get_encoding(self, source):
        """Get encoding in which "list_files" function is expected to return file names.

        :type source: parallels.plesk.source.web.connections.WebSource
        :rtype: str
        """
        # All files with suspicious symbols are retrieved with filesystem runner
        return source.get_filesystem_runner_encoding()

    def _fetch_files_and_dirs_with_suspicious_symbols_with_filesystem_runner(self, source, tree, parent_items=None):
        """
        :type source: parallels.plesk.source.web.connections.WebSource
        :type tree: list
        :type parent_items: list[str | unicode]
        :rtype: list
        """
        if parent_items is None:
            parent_items = [item for item in source.get_files_root().split('/') if item != '']

        new_tree = []

        existing_item_names = set()
        tree_has_suspicious_items = False

        if sys.version_info.major >= 3:
            str_type = str
        else:
            str_type = unicode

        for item in tree:
            # Process file
            if isinstance(item, str) or isinstance(item, str_type):
                if self._has_suspicious_symbols(item):
                    tree_has_suspicious_items = True
                else:
                    new_tree.append(item)
                    existing_item_names.add(item)
            # Process directory
            elif isinstance(item, dict):
                new_item = {}

                for dir_name, subtree in item.items():
                    if self._has_suspicious_symbols(dir_name):
                        tree_has_suspicious_items = True
                    else:
                        new_item[dir_name] = self._fetch_files_and_dirs_with_suspicious_symbols_with_filesystem_runner(
                            source, subtree, parent_items + [dir_name]
                        )
                        existing_item_names.add(dir_name)

                new_tree.append(new_item)

        if tree_has_suspicious_items:
            full_path = "/".join(parent_items)
            with logger.debug_level_context(
                messages.DIRECTORY_WITH_SUSPICIOUS_ITEMS,
                directory=full_path,
                transport=source.get_filesystem_runner_title()
            ):
                with source.filesystem_runner() as runner:
                    assert isinstance(runner, BaseRunner)

                    with logger.debug_level_context(
                        messages.DIRECTORY_WITH_SUSPICIOUS_ITEMS_LIST_ITEMS,
                        directory=full_path,
                        transport=source.get_filesystem_runner_title()
                    ):
                        file_list_extended = runner.get_files_list_extended(full_path)

                    for item in file_list_extended:
                        if item.name in existing_item_names:
                            continue

                        if item.is_dir:
                            with logger.debug_level_context(
                                messages.DIRECTORY_WITH_SUSPICIOUS_ITEMS_ADD_DIR,
                                name=item.name, transport=source.get_filesystem_runner_title()
                            ):
                                new_tree.append({
                                    item.name: runner.get_dir_tree("/" + "/".join(parent_items + [item.name]))
                                })
                        else:
                            logger.fdebug(
                                messages.DIRECTORY_WITH_SUSPICIOUS_ITEMS_ADD_FILE,
                                name=item.name, transport=source.get_filesystem_runner_title()
                            )
                            new_tree.append(item.name)

        return new_tree

    @staticmethod
    def _has_suspicious_symbols(s):
        """
        :type s: str | unicode 
        :rtype: bool 
        """
        return not is_ascii_string(s) or '?' in s
