from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.migrator_config import PhysicalServerConfig
from parallels.core.utils.common import find_only, cached
from parallels.core.registry import Registry
from parallels.core.dump.dump import SubscriptionNotFoundException

logger = get_logger(__name__)


class GlobalMigrationContext(object):
    """
    :type migrator_server: parallels.core.connections.migrator_server.MigratorServer
    :type target_panel_obj: parallels.core.target_panel_base.TargetPanelBase
    :type target_model: parallels.core.target_data_model.Model
    :type hosting_repository: parallels.core.hosting_repository.model.HostingRepositoryModel
    :type progress: parallels.core.utils.migration_progress.MigrationProgress
    :type subscriptions_status: parallels.core.utils.session_subscriptions_status.SessionSubscriptionsStatus
    :type subscriptions_report: parallels.core.utils.session_subscription_report.SessionSubscriptionsReport
    :type convert_report: parallels.core.reports.model.report.Report
    :type pre_check_report: parallels.core.reports.model.report.Report
    :type session_files: parallels.core.session_files.CommonSessionFiles
    :type target_existing_objects: parallels.core.existing_objects_model.ExistingObjectsModel
    :type statistics_reporter: parallels.core.statistics.StatisticsReporter
    :type source_panel_type: str | unicode
    :type source_panel_dir: str | unicode
    :type target_panel_type: str | unicode
    :type session_statistics: parallels.core.statistics.SessionStatistics
    :type start_time: float
    :type local_temp_filename: parallels.core.utils.local_temp_filename.LocalTempFilename
    :type cache_state_controllers: parallels.core.utils.cache.CacheStateControllers
    :type mssql_servers_without_physical_access: (
        parallels.core.utils.mssql_servers_without_physical_access.MSSQLServersWithoutPhysicalAccess
    )
    :type migration_list_data: parallels.core.migration_list.entities.list_data.MigrationListData
    :type post_migration_check_report: parallels.core.reports.model.report.Report
    :type post_migration_check_report_writer: parallels.core.reports.multi_report_writer.MultiReportWriter
    :type execution_migration_report: parallels.core.reports.model.report.Report
    :type migration_report_writer: parallels.core.reports.multi_report_writer.MultiReportWriter
    :type dns_forwarding_report: parallels.core.reports.model.report.Report
    :type dns_forwarding_report_writer: parallels.core.reports.multi_report_writer.MultiReportWriter
    :type ssh_key_pool: parallels.core.utils.ssh_key_pool.SSHKeyPool
    :type user_ssh_access_pool: parallels.core.utils.user_ssh_access_pool.UserSSHAccessPool
    :type name_mapping: parallels.core.utils.name_conflicts.NameMapping
    :type agent_options: dict
    :type stop_mark: parallels.core.utils.stop_mark.StopMark
    """
    def __init__(self):
        self.conn = None  # connections to source and target servers
        self.session_files = None  # utility class to construct paths to session files
        self.migrator_server = None
        self.hosting_repository = None
        self.safe = None
        self.source_panel_type = None
        self.source_panel_dir = None
        self.target_panel_type = None
        self.target_panel_obj = None
        self.target_model = None
        self.load_raw_dump = None
        self.load_converted_dump = None
        self.load_shallow_dump = None
        self.sources = None
        self.source_servers = None
        self.migration_list_data = None
        self.ip_mapping = None
        self.iter_windows_servers = None
        self.iter_all_subscriptions_unfiltered = None
        self.options = None
        self.password_holder = None
        self.config = None
        self.target_existing_objects = None
        self.source_has_dns_forwarding = True
        self.hosting_analyzer_enabled = True
        self.post_migration_checks_enabled = True
        self.ssh_key_pool = None
        self.user_ssh_access_pool = None
        self.rsync_pool = None
        self.get_rsync = None
        self.progress = None
        self.subscriptions_status = None
        self.subscriptions_report = None
        self.statistics_reporter = None
        self.session_statistics = None
        self.start_time = None
        self.dns_forwarding_report = None
        self.dns_forwarding_report_writer = None
        self.convert_report = None
        self.pre_check_report = None
        self.execution_migration_report = None
        self.persistent_migration_report = None
        self.migration_report_writer = None
        self.post_migration_check_report = None
        self.post_migration_check_report_writer = None
        self.application_adjust_report = None
        self.test_services_report = None
        self.local_temp_filename = None
        self.changed_security_policy_nodes = set()
        self.cache_state_controllers = None
        # List of dumps agents that were deployed during this migration tool's execution
        # Fill the list to uninstall agent once migration was finished
        self.deployed_dump_agents = []
        self.mssql_servers_without_physical_access = None
        # Dictionary: [source panel ID][configuration option name] = configuration option value
        self.agent_options = {}
        self.name_mapping = None
        self.stop_mark = None

        # XXX Migrator object, for legacy actions - do not use for new code
        # avoid usage for old code
        self.migrator = None

    def iter_all_subscriptions(self):
        """List subscriptions which should be migrated (completely failed subscriptions are excluded)

        :rtype: list[parallels.core.migrated_subscription.MigratedSubscription]
        """
        return self.migrator.iter_all_subscriptions()

    def has_subscription(self, name):
        for subscription in self.iter_all_subscriptions():
            if subscription.name == name:
                return True
        return False

    def get_subscription(self, name):
        """Retrieve subscription with given name

        :rtype: parallels.core.migrated_subscription.MigratedSubscription
        """
        for subscription in self.iter_all_subscriptions():
            if subscription.name == name:
                return subscription
        raise SubscriptionNotFoundException(name)

    def get_source_info(self, source_id):
        """
        :raises: Exception
        :rtype: parallels.core.global_context.SourceInfo
        """
        return find_only(
            self._sources_info, lambda s: s.id == source_id,
            messages.UNABLE_RETRIEVE_SOURCE_INFO_S % source_id
        )

    def get_sources_info(self, source_ids=None):
        """Return information about sources that could have backup dumps filtered by specified list

        See _sources_info function documentation for more details.

        :rtype: list[parallels.core.global_context.SourceInfo]
        """
        if source_ids is None:
            return self._sources_info
        else:
            return [source_info for source_info in self._sources_info if source_info.id in source_ids]

    def get_source_servers_info(self):
        """Information about hosting servers that are considered primary source of hosting information

        In case of source Expand, it includes only Plesk servers.
        Be careful: centralized mail servers are not included in this list! They could have backup dump,
        but they are not primary sources: centralized mail server backup dumps are used only to
        restore mail settings, but not customers, resellers, web hosting settings, etc.

        In case of source legacy panels, there is usually only one source server which
        has one backup dump used as source of hosting information.

        In case of source Plesk, there could be one or multiple Plesk servers.

        In case of custom panel, backup dump file specified by customer is used as a source.

        :rtype: list[parallels.core.global_context.SourceInfo]
        """
        return [source_info for source_info in self.get_sources_info() if source_info.is_server]

    def get_primary_sources_info(self):
        """
        :rtype list[parallels.core.global_context.SourceInfo]
        """
        return self.get_source_servers_info()

    @property
    @cached
    def _sources_info(self):
        """Information about ALL sources of backup dumps.

        In case of source Expand, it includes both Plesk servers and centralized mail servers. As centralized mail
        servers are actually Plesk servers too, we dump hosting settings of centralized mail as backup dump.

        In case of source legacy panels, there is usually only one source server which has one backup dump.

        In case of source Plesk, there could be one or multiple Plesk servers.

        In case of custom panel, backup dump file specified by customer is used as a source.

        Here is a table that summarizes different sources of backup dumps:
        +--------------------------------------------+----------------------------+-------------+
        |              Source server                 | Is primary source of data? |  Is server? |
        +--------------------------------------------+----------------------------+-------------+
        | Expand Plesk server                        |          Yes               |    Yes      |
        | Expand centralized mail server             |          No                |    Yes      |
        | Legacy panel server (Confixx, cPanel, etc) |          Yes               |    Yes      |
        | Plesk server                               |          Yes               |    Yes      |
        | Custom panel                               |          Yes               |    No       |
        +--------------------------------------------+----------------------------+-------------+

        :rtype list[parallels.core.global_context.SourceInfo]
        """
        return [SourceInfo(source) for source in self.sources]


class SourceInfo(object):
    def __init__(self, source):
        self._source = source

    @property
    def source(self):
        return self._source

    @property
    def source_id(self):
        return self._source.source_id

    @property
    def id(self):
        return self.source_id

    @property
    def config(self):
        return self._source.config

    @property
    def settings(self):
        return self.config

    @property
    def is_server(self):
        return isinstance(self.config, PhysicalServerConfig)

    @cached
    def load_raw_dump(self):
        """Load full raw dump for this source

        :rtype: parallels.core.dump.dump.PleskBackupSource
        """
        return Registry.get_instance().get_context().load_raw_dump(self.config)

    def load_shallow_dump(self):
        """Load shallow dump for this source

        Shallow dump has high-level data about high-level objects only: list of customers, subscriptions, resellers,
        and so on
        """
        return Registry.get_instance().get_context().load_shallow_dump(self.config)

    @property
    def ip(self):
        return self.config.ip if self.is_server else None

    @property
    def is_windows(self):
        if not self.is_server:
            raise NotImplementedError()
        return self.config.is_windows
