import os

from parallels.core import get_logger
from parallels.core.connections.source_server import SourceServer
from parallels.core.panels import AdditionalSourcePanelConfig
from parallels.core.registry import Registry
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.utils.thirdparties import packages
from parallels.core.utils.thirdparties.deployer import ThirdpartyDeployer
from parallels.plesk.source.custom import messages
from parallels.plesk.source.custom.actions.deploy_migrator_python import DeployMigratorPython

logger = get_logger(__name__)


class AgentPool(object):
    AGENT_OUTPUT_DIR = 'output'
    PATH_PREFIX = os.path.join('lib', 'python')
    PLESK_MIGRATOR_DIR = 'plesk_migrator'
    MIGRATOR_SDK_DIR = 'sdk'
    INIT_FILENAME = '__init__.py'

    def __init__(self):
        self._servers = {}

    def is_empty(self):
        return len(self._servers) == 0

    def deploy(self, server, source_panel):
        """Deploy agent if required to source server
        :type server: parallels.core.connections.source_server.SourceServer
        :type source_panel: parallels.core.panels.AdditionalSourcePanelConfig
        :rtype: None
        """
        ip = server.ip()
        if ip not in self._servers:
            self._deploy(server, source_panel)
            self._servers[ip] = server

    def remove_all(self):
        for server in self._servers.values():
            if not isinstance(server, SourceServer):
                continue
            agent_session_file_path = server.get_session_file_path('agent')
            with server.runner() as runner:
                assert isinstance(runner, BaseRunner)
                logger.finfo(
                    messages.REMOVE_AGENT_FROM_SOURCE, server=server.description()
                )
                runner.remove_directory(agent_session_file_path)
        self._servers.clear()

    @classmethod
    def _deploy(cls, server, source_panel):
        """Deploy agent and required components (SDK) to source server
        :type server: parallels.core.connections.source_server.SourceServer
        :type source_panel: parallels.core.panels.AdditionalSourcePanelConfig
        :rtype: None
        """
        local_agent_dir = os.path.join(source_panel.extension_dir, 'agent')
        agent_session_file_path = server.get_session_file_path('agent')

        # Paths for copying SDK to source
        base_dir = Registry.get_instance().get_base_dir()
        # local paths
        local_plesk_migrator_dir = os.path.join(base_dir, cls.PATH_PREFIX, cls.PLESK_MIGRATOR_DIR)
        local_init_file = os.path.join(local_plesk_migrator_dir, cls.INIT_FILENAME)
        local_migrator_sdk_dir = os.path.join(local_plesk_migrator_dir, cls.MIGRATOR_SDK_DIR)
        # target server paths
        target_plesk_migrator_dir = server.join_file_path(agent_session_file_path, cls.PLESK_MIGRATOR_DIR)
        target_init_file = server.join_file_path(target_plesk_migrator_dir, cls.INIT_FILENAME)
        target_migrator_sdk_dir = server.join_file_path(target_plesk_migrator_dir, cls.MIGRATOR_SDK_DIR)

        logger.finfo(messages.COPY_AGENT_TO_SOURCE, agent=source_panel.source_panel_id, server=server.description())
        with server.runner() as runner:
            assert isinstance(runner, BaseRunner)
            runner.upload_directory(local_agent_dir, None, agent_session_file_path)
            agent_bin = server.join_file_path(agent_session_file_path, source_panel.entry_point)
            if not server.is_windows():
                runner.execute_command('chmod +x {agent_bin}', dict(agent_bin=agent_bin))
            agent_output_dir = server.join_file_path(agent_session_file_path, cls.AGENT_OUTPUT_DIR)
            runner.mkdir(agent_output_dir)

            # Upload Plesk migrator SDK to source
            runner.mkdir(target_plesk_migrator_dir)
            runner.upload_file(local_init_file, target_init_file)
            runner.upload_directory(local_migrator_sdk_dir, None, target_migrator_sdk_dir)


def get_agent_run_command(server, source_panel):
    """Get command which can be used for manual agent execution
    :type server: parallels.core.connections.source_server.SourceServer
    :type source_panel: parallels.core.panels.AdditionalSourcePanelConfig
    :rtype: str | unicode
    """
    command, args, _ = _get_agent_command(server, source_panel)
    with server.runner() as runner:
        assert isinstance(runner, BaseRunner)
        return runner.format_command_for_log(
            command, args
        )


def run_agent(server, source_panel):
    """Run custom panel agent on source server (agent must be deployed)
    :type server: parallels.core.connections.source_server.SourceServer
    :type source_panel: parallels.core.panels.AdditionalSourcePanelConfig
    :rtype: None
    """
    logger.finfo(messages.RUN_AGENT_ON_SOURCE, agent=source_panel.source_panel_id, server=server.description())
    command, args, working_dir, = _get_agent_command(server, source_panel)
    with server.runner() as runner:
        assert isinstance(runner, BaseRunner)
        return runner.execute_command(
            command, args, working_dir

        )


def _get_agent_command(server, source_panel):
    agent_session_file_path = server.get_session_file_path('agent')
    agent_bin = server.join_file_path(agent_session_file_path, source_panel.entry_point)
    with server.runner() as runner:
        assert isinstance(runner, BaseRunner)
        if source_panel.execution == AdditionalSourcePanelConfig.EXTENSION_ENTRY_POINT_MIGRATOR_PYTHON:
            if server.is_windows():
                python_bin = ThirdpartyDeployer.get_instance().get_executable(
                    packages.PythonWindowsAdditionalPanels, server
                )
            else:
                python_bin = DeployMigratorPython.PYTHON_BIN_UNIX
            return (
                "{python_bin} {agent_bin}",
                dict(
                    agent_bin=agent_bin,
                    python_bin=python_bin
                ),
                ExecutionOptions(working_dir=agent_session_file_path)
            )
        else:
            return (
                "{agent_bin}",
                dict(
                    agent_bin=agent_bin
                ),
                ExecutionOptions(working_dir=agent_session_file_path)
            )
