from parallels.core import messages, get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionResults
from parallels.core.utils.common import cached
from parallels.core.utils.http_client import perform_http_request_response_text

logger = get_logger(__name__)


class WebRPCAgentRunner(BaseRunner):
    """
    :type _web_rpc_agent: parallels.core.runners.web.utils.agent.WebRPCAgent
    """

    def __init__(self, web_rpc_agent, hostname):
        """
        :type hostname: str
        :type web_rpc_agent: parallels.core.runners.web.utils.WebAgent
        """
        super(WebRPCAgentRunner, self).__init__(None, hostname)
        self._web_rpc_agent = web_rpc_agent

    def connect(self):
        self._web_rpc_agent.connect()

    @property
    @cached
    def is_windows(self):
        return self._web_rpc_agent.is_windows()

    @property
    @cached
    def php_version(self):
        return self._web_rpc_agent.get_php_version()

    @cached
    def get_absolute_filesystem_root_path(self):
        return self._web_rpc_agent.get_absolute_filesystem_root_path()

    def file_exists(self, file_path):
        return self._web_rpc_agent.is_file_exists(file_path)

    def get_files_list(self, path):
        return self._web_rpc_agent.get_file_names(path)

    def iter_files_list_nested(self, path):
        return self._web_rpc_agent.get_file_paths(path, True)

    def get_dir_tree(self, path):
        """Retrieve tree structure of the directory with given path, like the following:

        [
            'file_1',
            'file_2',
            {
                'dir_1': [
                    'file_3'
                ]
            }
        ]

        :type path: str | unicode
        :rtype: list[str | {str, list}]
        """
        def process_raw_tree(raw_tree):
            converted_tree = []

            if raw_tree is None:
                raw_tree = []

            for item in raw_tree:
                if isinstance(item, basestring):
                    converted_tree.append(item)
                    continue

                if not isinstance(item, dict):
                    continue
                if len(item) != 1:
                    continue

                dir_name, dir_raw_tree = item.items().pop()
                converted_tree.append({dir_name: process_raw_tree(dir_raw_tree)})
            return converted_tree

        # the directory tree, retrieved via web RPC agent, store nested directories as a dict,
        # so convert it into tuple to conform with other runners
        return process_raw_tree(self._web_rpc_agent.get_dir_tree(path))

    def mkdir(self, dir_path):
        self._web_rpc_agent.mkdir(dir_path)

    def get_file(self, remote_file_path, local_file_path):
        self._web_rpc_agent.download_file(remote_file_path, local_file_path)

    def get_file_contents(self, remote_filename):
        """Get contents of the file.

        :type remote_filename: str | unicode
        :rtype: str
        """
        return self._web_rpc_agent.get_file_contents(remote_filename)

    def get_file_size(self, remote_filename):
        # TODO: implement retrieving of actual file size on remote web source
        return 0

    def upload_file(self, local_filename, remote_filename):
        """
        :type local_filename: str | unicode
        :type remote_filename: str | unicode
        :rtype: None
        """
        self._web_rpc_agent.upload_file(local_filename, remote_filename)

    def upload_file_content(self, file_path, content):
        self._web_rpc_agent.upload_file_contents(file_path, content)

    def remove_file(self, filename):
        self._web_rpc_agent.remove_file(filename)

    def run_php(self, php_code):
        with self._web_rpc_agent.deploy_probe(php_code) as probe_url:
            logger.fdebug(messages.REQUEST_PHP_PROBE, url=probe_url)
            return perform_http_request_response_text(
                probe_url, verify_certificates=self._web_rpc_agent.verify_certificates
            )

    def piped_sh_unchecked(
        self, command, arguments, execution_options,
        consume_stdout_function, consume_stderr_function, finalize_function, error_function
    ):
        """Execute command, and read stdout/stderr dynamically, as it goes.

        Stderr will be dropped on WebRpcAgent's side if it runs on Windows. This behaviour is caused by PHP
        limitations on Windows. PHP function stream_select does not work correct on Windows. There are no proper
        way available to handle both stdout and stderr pipes in Windows, avoid possible lock and keep compatibility
        with different PHP versions and environment.

        Consume stdout/stderr function is called once we get the next portion of data from command's stdout/stderr.
        The only argument it accepts is that data (as byte string).

        Once the command is finished, finalize function is called. The only argument is exit code of the command.

        :type command: str | unicode
        :type arguments: dict | None
        :type execution_options: parallels.core.runners.entities.ExecutionOptions
        :type consume_stdout_function: (str) -> None
        :type consume_stderr_function: (str) -> None
        :type finalize_function: (int) -> None
        :type error_function: (str | unicode) -> None
        """
        self._web_rpc_agent.piped_sh_unchecked(
            command, arguments, execution_options,
            consume_stdout_function, consume_stderr_function, finalize_function
        )

    def _execute_command_raw(self, command, arguments, execution_options):
        """Execute command and return results, but do not log, do not check for exit code, and so on.

        :type command: str | unicode 
        :type arguments: dict[str | unicode, str | unicode] 
        :type execution_options: parallels.core.runners.entities.ExecutionOptions 
        :rtype: parallels.core.runners.entities.ExecutionResults 
        """
        exit_code, stdout, stderr = self._web_rpc_agent.sh_unchecked(
            command, arguments, execution_options
        )
        return ExecutionResults(exit_code, stdout.encode('utf-8'), stderr.encode('utf-8'))

    def _format_command(self, cmd, args=None):
        """Format command arguments passed for 'sh_unchecked' methods into command line string

        This command line will be added to log and error messages, so it is expected that you could
        simply copy-paste it from logs or migrator output, and execute.

        :type cmd: str | unicode
        :type args: dict[str | unicode, str | unicode] | None
        :rtype: str | unicode
        """
        return self._web_rpc_agent.format_command(cmd, args)
