import os

from parallels.core import get_logger
from parallels.core.reports.model.issue import Issue
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions, ControlledInterruption
from parallels.core.runners.exceptions.non_zero_exit_code import NonZeroExitCodeException
from parallels.core.utils.common import safe_string_repr, safe_format
from parallels.core.utils.file_utils import temp_filename, get_relative_path
from parallels.core.utils.unix_utils import copy_file_secure
from parallels.plesk.source.web import messages
from parallels.plesk.source.web.utils.copy_content.base import CopyContentBase


logger = get_logger(__name__)


class CopyContentLinuxSSHBased(CopyContentBase):
    """Base class for SSH-based methods to copy file: SCP and rsync"""

    def copy_folder(self, global_context, folder):
        """Run action for single migrated folder

        :type global_context: parallels.plesk.source.web.global_context.WebGlobalMigrationContext
        :type folder: parallels.plesk.source.web.migrated_folder.MigratedFolder
        :rtype: list[parallels.core.reports.model.issue.Issue] | None
        """
        issues = []

        controlled_interruption = ControlledInterruption(stop_mark=global_context.stop_mark)
        source = global_context.conn.get_source()

        with logger.debug_level_context(messages.CREATE_TARGET_DIRECTORY):
            # create directory recursively ("mkdir -p") as rsync/scp are not able to create
            # directory when parent directory does not exist
            plesk_dir = global_context.conn.target.plesk_server.plesk_dir
            filemng = os.path.join(plesk_dir, 'admin', 'bin', 'filemng')
            system_user_login = global_context.site_info_storage.get_target_sysuser_login()
            with global_context.conn.target.plesk_server.runner() as runner:
                assert isinstance(runner, BaseRunner)
                runner.execute_command('{filemng} {user} mkdir -p {dir}', dict(
                    filemng=filemng, user=system_user_login, dir=folder.target_path
                ))

        with logger.debug_level_context(messages.CONFIGURE_KEY_SSH_ACCESS):
            ssh_key_owned_by_root = global_context.ssh_key_pool.get(
                source.get_ssh_server(), global_context.migrator_server
            ).key_pathname
            logger.fdebug(messages.SSH_PRIVATE_KEY_LOCATION, private_key_location=ssh_key_owned_by_root)

        with logger.debug_level_context(messages.PRIVATE_KEY_TO_USER):
            target_plesk_server = global_context.conn.target.plesk_server
            target_vhost_dir = target_plesk_server.get_vhost_dir(
                global_context.site_info_storage.get_target_subscription()
            )

            user_ssh_key_in_virtual_host = temp_filename(target_vhost_dir, 'site-import-rsync-ssh-key')

            with target_plesk_server.runner() as runner:
                copy_file_secure(
                    runner=runner,
                    user=system_user_login,
                    source_file_path=ssh_key_owned_by_root,
                    target_file_path=user_ssh_key_in_virtual_host,
                )
            logger.fdebug(
                messages.PRIVATE_KEY_USER_DETAILS,
                private_key_location=user_ssh_key_in_virtual_host, sysuser_login=system_user_login
            )

        try:
            with logger.debug_level_context(messages.TRANSFER_FILES, method=self.get_method_title()):
                with folder.target_server.runner() as local_runner:
                    source_dir = folder.source_path.rstrip('/')
                    target_dir = folder.target_path.rstrip('/')

                    excludes = [
                        get_relative_path(
                            exclude, folder.source_path,
                            is_windows=False, sub_path_is_windows=False, base_path_is_windows=False
                        )
                        for exclude in folder.path_excludes
                    ]

                    command = self._get_copy_command(
                        source.config.host.encode('idna'), source.config.username, source.config.ssh_port,
                        user_ssh_key_in_virtual_host, source_dir, target_dir,
                        excludes
                    )
                    try:
                        local_runner.execute_command(
                            u'su {username} --shell /bin/sh -c {command}', dict(
                                username=folder.target_sysuser_login,
                                command=command
                            ),
                            ExecutionOptions(
                                controlled_interruption=controlled_interruption,
                                umask=000
                            )
                        )
                    except NonZeroExitCodeException as e:
                        # Generally, in case of error we show only short message to customer, without any details.
                        # The details, including command output, are visible to Plesk administrator only.
                        # That is necessary to avoid showing scary messages and messages which could accidentally
                        # expose security details to customers.
                        #
                        # But it is a common situation when rsync/scp failed to copy only several files
                        # (e.g. because of permissions problem).
                        # Handle the issue manually, so customer could see the output of rsync/scp
                        # (which could show which files were not copied) - that is quite safe.
                        # The output is a bit scary, but we can not do anything with that.
                        logger.exception()
                        issues.append(
                            Issue(
                                'copy-content-ssh-failed', severity=Issue.SEVERITY_ERROR,
                                problem_text=safe_format(
                                    messages.ACTION_COPY_FILES_SSH_FAILED_FOR_FOLDER,
                                    source_folder=folder.source_path,
                                    target_folder=folder.target_path,
                                    method=self.get_method_title(),
                                    command=command,
                                    output=safe_format("{stdout}\n{stderr}", stdout=e.stdout, stderr=e.stderr)
                                ),
                                details_text=safe_string_repr(e)
                            )
                        )
        finally:
            with logger.debug_level_context(messages.REMOVE_PRIVATE_KEY):
                os.remove(user_ssh_key_in_virtual_host)

        return issues

    def _get_copy_command(
        self, source_host, source_username, source_port, private_ssh_key, source_dir, target_dir, excludes
    ):
        """Get shell command which should be used to copy directories using SSH

        :type source_host: str | unicode
        :type source_username: str | unicode
        :type source_port: int | None
        :type private_ssh_key: str | unicode
        :type source_dir: str | unicode
        :type target_dir: str | unicode
        :type excludes: list[str | unicode]
        :rtype: str | unicode
        """
        raise NotImplementedError()

    def get_method_title(self):
        """Get transfer method title as string

        :rtype: str | unicode
        """
        raise NotImplementedError()
