import os
import sys

import parallels.plesk.source.legacy
from parallels.core import MigrationError
from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.utils import migrator_utils
from parallels.core.utils.json_utils import write_json
from parallels.core.utils.pmm.agent import PmmMigrationAgentBase, DumpAll
from parallels.core.utils.unix_utils import format_command
from parallels.plesk.source.legacy import messages

logger = get_logger(__name__)


class UnixPmmMigrationAgent(PmmMigrationAgentBase):
    """ A wrapper for legacy migration agents."""

    def __init__(self, global_context, server, migrator_pmm_dir, settings):
        """Object constructor.

        Arguments:
            global_context: registry for access to global migration storage
            server: an instance of class 'SourceServer'
            migrator_pmm_dir: a directory with migration agent app code
            settings: a config.ini section with source server settings
        """

        self.migrator_pmm_dir = migrator_pmm_dir
        self.settings = settings

        super(UnixPmmMigrationAgent, self).__init__(global_context, server)

    @property
    def common_pmm_dir(self):
        return migrator_utils.get_package_extras_file_path(parallels.plesk.source.legacy, 'plesk_11_5_pmm_shared')

    def _run(self, dump_xml_filename, dump_log_filename, selection=DumpAll()):
        """Run migration agent on a source panel and download the results.
        
        Execute the agent which puts results into XML dump file.
        Download dump XML file and log file to specified locations:
        - dump_xml_filename - local filename to put dump XML to
        - dump_log_filename - local filename to put dump log to

        Raise MigrationError in case of any failure.
        """
        self._run_agent_script(
            script_filename=self.settings.agent_script_name,
            local_data_filename=dump_xml_filename, remote_data_filename='dump.xml',
            local_log_filename=dump_log_filename, remote_log_filename='dump.log',
            log_step_info_msg=messages.LOG_STEP_CREATE_SOURCE_PANEL_DUMP_XML,
            log_execute_msg=messages.EXECUTE_CREATE_DUMP_XML,
            log_download_data_file_msg=messages.LOG_DOWNLOAD_DUMP_LOG,
            log_download_log_file_msg=messages.LOG_DUMP_XML_TO,
            error_run_script_msg=messages.ERROR_CREATING_DUMP,
            error_download_log_msg=messages.ERROR_DOWNLOADING_DUMP_LOG,
        )

    def check(self):
        """Execute pre-migration checks on a source panel.

        Raise MigrationError in case of any failure.

        Returns:
            Path to generated report file.
        """
        self._run_agent_script(
            script_filename='PreMigration.pl',
            local_data_filename=self.check_report_file,
            remote_data_filename='pre-migration.xml',
            local_log_filename=self.check_log_file,
            remote_log_filename='pre-migration.log',
            log_step_info_msg=messages.RUN_PRE_MIGRATION_CHECKS,
            log_execute_msg=messages.EXECUTE_PREMIGRATION_SCRIPT,
            log_download_data_file_msg=messages.DOWNLOAD_PRE_MIGRATION_LOG,
            log_download_log_file_msg=messages.DOWNLOAD_PREMIGRATION_REPORT,
            error_run_script_msg=messages.ERROR_PERFORMING_PRE_MIGRATION,
            error_download_log_msg=messages.ERROR_DOWNLOADING_PRE_MIGRATION_LOG
        )

        return self.check_report_file

    def _run_agent_script(
        self,
        script_filename,
        local_data_filename, remote_data_filename,
        local_log_filename, remote_log_filename,
        log_step_info_msg, log_execute_msg,
        log_download_data_file_msg,
        log_download_log_file_msg,
        error_run_script_msg,
        error_download_log_msg
    ):
        logger.info(log_step_info_msg)

        try:
            with self._source_server.runner() as runner:
                assert isinstance(runner, BaseRunner)
                logger.debug(log_execute_msg)
                try:
                    runner.execute_command(
                        u'cd {path}; {perl} {script} --dump-all --configuration-only --server -v 5 > {log} 2>&1',
                        dict(
                            perl=self._perl_bin,
                            path=self.agent_dir,
                            script=script_filename,
                            log=remote_log_filename,
                        )
                    )
                    logger.debug(
                        log_download_data_file_msg, local_data_filename
                    )
                    runner.get_file(
                        os.path.join(
                            self.agent_dir, 
                            remote_data_filename
                        ),
                        local_data_filename
                    )
                finally:  # always try to download log, even if script failed
                    try:
                        logger.debug(
                            log_download_log_file_msg, local_log_filename
                        )
                        runner.get_file( 
                            os.path.join(
                                self.agent_dir, 
                                remote_log_filename
                            ),
                            local_log_filename
                        )
                    except Exception as e:
                        logger.exception()
                        logger.error(error_download_log_msg % e)

        except Exception as e:
            logger.exception()
            raise MigrationError(error_run_script_msg % e)

    def _deploy(self):
        """Deploy migration agent files to a source panel server.
        
        Return path to the directory, where the agent has been deployed.
        """
        logger.finfo(messages.DEPLOY_MIGRATION_AGENT, server_ip=self._source_server.ip())
        self._cleanup_dir()
        self._deploy_agent_sources()
        self._deploy_agent_config()
        return self._agent_dir

    def _cleanup_dir(self):
        """Clean up directory with agent files on the source server

        :rtype: None
        """
        with self._source_server.runner() as runner:
            runner.remove_directory(self._agent_dir)

    def _deploy_agent_sources(self):
        """Deploy agent Perl sources to the source server

        :rtype: None
        """
        key_pathname = self.global_context.ssh_key_pool.get(
            self._source_server, self.global_context.migrator_server
        ).key_pathname
        for local_lib_dir in (self.common_pmm_dir, self.migrator_pmm_dir):
            with self.global_context.migrator_server.runner() as runner:
                assert isinstance(runner, BaseRunner)
                port = self._source_server.settings().ssh_auth.port
                if sys.version_info.major >= 3:
                    port = str(port)
                shell_command = format_command(
                    'ssh -i {key} -p {port} '
                    '-o PasswordAuthentication=no -o StrictHostKeyChecking=no -o GSSAPIAuthentication=no',
                    key=key_pathname,
                    port=port
                )
                runner.execute_command(
                    "rsync -r --chmod=Fu=r,Du=rwx,go= --timeout=30 "
                    "-e {ssh_command} "
                    "{local_dir}/ {ssh_user}@{remote_ip}:{remote_dir}", dict(
                        local_dir=local_lib_dir,
                        ssh_user=self._source_server.settings().ssh_auth.username,
                        remote_ip=self._source_server.ip(),
                        remote_dir=self._agent_dir,
                        ssh_command=shell_command
                    )
                )

    def _deploy_agent_config(self):
        """Deploy agent configuration file (config.json) with additional parameters passed to the agent

        :rtype: None
        """
        source_id = self._source_server.source_id
        local_agent_config = self.global_context.session_files.get_agent_config_file(source_id)
        write_json(local_agent_config.path, self.global_context.agent_options.get(source_id), pretty_print=True)
        with self._source_server.runner() as runner:
            runner.upload_file(
                local_agent_config.path,
                self._source_server.join_file_path(self._agent_dir, 'config.json')
            )

    @property
    def _agent_dir(self):
        """Path to agent directory on the source server

        :rtype: str | unicode
        """
        return self._source_server.get_session_file_path('pmm_agent')

    def uninstall(self):
        """Remove agent files from the source server."""
        with self._source_server.runner() as runner:
            if self.agent_dir:
                runner.remove_directory(self.agent_dir)
