import os

from parallels.core.logging import get_logger
from parallels.core.utils.pmm.agent import DumpAll, DumpSelected
from parallels.core.utils.yaml_utils import read_yaml, write_yaml
from parallels.plesk.source.cpanel import messages
from parallels.core.dump.data_model import DatabaseServer
from parallels.plesk.source.cpanel.content.mail import CpanelSourceMailDirectory
from parallels.plesk.source.cpanel.content.mail import CpanelCopyMailRsync
from parallels.plesk.source.cpanel.migrated_subscription import CPanelMigratedSubscription
from parallels.plesk.source.cpanel.web_files import CpanelWebFiles
from parallels.plesk.source.plesk.infrastructure_checks import checks as infrastructure_checks
from parallels.core.utils.common import group_by, cached
from parallels.plesk.source.cpanel import connections
from parallels.plesk.source.legacy.migrator import PmmUnixMigrator
from parallels.plesk.source.cpanel.global_context import CpanelGlobalMigrationContext
from parallels.core.utils.database_utils import copy_db_content_linux, LinuxDatabaseTransferOptions

logger = get_logger(__name__)


class Migrator(PmmUnixMigrator):

    def shallow_dump_supported(self, source_id):
        return True

    def fetch_source_shallow(self, options, overwrite):
        dumpfile = self.get_shallow_dump_filename(self.global_context.settings.PANEL_ID)
        if not overwrite and os.path.exists(dumpfile):
            logger.info(messages.SHALLOW_DUMP_ALREADY_EXISTS_SKIP)
        else:
            self.global_context.dump_agent.create_shallow_dump(dumpfile)

    def get_shallow_dump_filename(self, server_id):
        return self.global_context.session_files.get_path_to_shallow_plesk_backup(server_id)

    def _fetch_source(self, options, overwrite):
        if self.global_context.migration_list_data is None:
            selection = DumpAll()
        else:
            selection = DumpSelected(
                resellers=set(self.global_context.migration_list_data.resellers),
                clients=set(self.global_context.migration_list_data.customers_mapping.keys()),
                domains=set(self.global_context.migration_list_data.subscriptions_mapping.keys())
            )
        selection_filename = self.global_context.session_files.get_path_to_raw_plesk_backup_selection('cpanel')
        with self.global_context.migrator_server.runner() as local_runner:
            if local_runner.file_exists(selection_filename):
                previous_selection = read_yaml(selection_filename)
            else:
                previous_selection = None

        dumpfile = self.get_raw_dump_filename(self.global_context.settings.PANEL_ID)
        if (
            previous_selection is not None and
            previous_selection.covers(selection) and
            os.path.exists(dumpfile) and
            not overwrite
        ):
            logger.info(messages.MIGRATION_DUMP_ALREADY_EXISTS_SKIP)
        else:
            self.global_context.dump_agent.create_dump(dumpfile, selection)
            write_yaml(selection_filename, selection)

    def _load_connections_configuration(self, global_context, target_panel_type):
        return connections.MigratorConnections(global_context, self._get_target_panel_by_name(target_panel_type))

    def _create_global_context(self):
        return CpanelGlobalMigrationContext()

    @cached
    def _create_migrated_subscription(self, name):
        return CPanelMigratedSubscription(self, name)

    @property
    def web_files(self):
        """Object to list files to be transferred from source to target

        :rtype: parallels.core.utils.paths.web_files.BaseWebFiles
        """
        return CpanelWebFiles()

    # ======================== copy mail content ==============================

    def copy_mail_content_single_subscription(self, subscription, issues):
        logger.info(messages.LOG_MIGRATE_MAIL_CONTENT_OF_SUBSCRIPTION, subscription.name)
        copy_action = CpanelCopyMailRsync(CpanelSourceMailDirectory)
        copy_action.copy_mail(self.global_context, subscription, issues)

    # ======================== databases =====================================

    def _get_src_db_server(self, database_server_host, database_server_port, database_server_type, server_dump):
        if database_server_type == 'mysql':
            user, password, host, port = self.global_context.dump_agent.get_mysql_credentials()
        else:
            user = 'postgres'
            password = None
            host = 'localhost'
            port = '5432'
        return DatabaseServer(
            host=host,
            port=port,
            login=user,
            password=password,
            dbtype=database_server_type,
            is_default=True,
        )

    def transfer_subscription_databases(self, subscription):
        databases_by_subscription = group_by(
            self._list_databases_to_copy(log_assimilated_databases=True),
            lambda db_info: db_info.subscription_name
        )
        subscription_databases = databases_by_subscription.get(subscription.name, [])

        original_owner = subscription.target_sysuser_name

        for db_info in subscription_databases:
            self._copy_database_content(db_info, original_owner)

    def _copy_database_content(self, db_info, original_owner):
        """Transfer MySQL, PostgreSQL database content.

        Original owner is a login name of database user that has admin
        permissions on all client's databases. In cPanel, that is a login of
        cPanel client.

        :type db_info: parallels.core.utils.database_utils.DatabaseInfo
        """

        safe = self._get_safe_lazy()

        if not self._check_database_exists_on_target(db_info):
            return

        repeat_error = messages.UNABLE_COPY_CONTENT_DATABASE_S_SUBSCRIPTION_1 % (
            db_info.source_database_name, db_info.subscription_name
        )

        keyfile = self.global_context.ssh_key_pool.get(
            db_info.source_database_server.utilities_server, db_info.target_database_server.utilities_server
        ).key_pathname
        transfer_options = LinuxDatabaseTransferOptions(
            keyfile=keyfile, dump_format='text', owner=original_owner)

        def _repeat_copy_linux_content():
            copy_db_content_linux(db_info, transfer_options)

        safe.try_subscription_with_rerun(
            _repeat_copy_linux_content, db_info.subscription_name,
            messages.FAILED_COPY_CONTENT_DATABASE_S_1 % db_info.source_database_name,
            is_critical=False, repeat_error=repeat_error, use_log_context=False)

    # ======================== infrastructure checks ==========================

    def _get_check_service_connection_commands(self):
        return {
            'web': self._check_unix_copy_web_content_rsync_connections,
            'mail': self._check_unix_copy_mail_content_rsync_connections,
            'db': self._check_unix_copy_db_content_scp_connections
        }

    def _check_infrastructure_connections(self, report, safe):
        logger.info(messages.LOG_CHECK_CONNECTION_REQUIREMENTS)

        for service in ('web', 'mail', 'db'):
            self._check_connection(service, report, safe)

    def _check_connection(self, service, report, safe):
        service_checkers = self._get_check_service_connection_commands()
        report_messages = {
            'title':
                messages.REPORT_TITLE_INFRASTRUCTURE_CHECKS_CONNECTIONS,
            'error':
                messages.FAILED_TO_CHECK_CONNECTIONS}
        report = report.subtarget(report_messages['title'] % service, None)
        checks = infrastructure_checks.InfrastructureChecks()
        with safe(report, report_messages['error'] % service):
            service_checkers[service](checks, report)

    def _check_disk_space(self, report, safe):
        logger.info(messages.LOG_CHECK_DISK_SPACE_REQUIREMENTS)
        disk_space_report = report.subtarget(messages.REPORT_TARGET_DISK_SPACE_REQUIREMENTS, None)
        super(Migrator, self)._check_disk_space_unix(disk_space_report)
