import threading

from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.actions.utils.multithreading_properties import MultithreadingProperties
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import group_by_id

logger = get_logger(__name__)


class DeploySubscription(SubscriptionAction):
    def __init__(self):
        self._lock = threading.Lock()

    def get_description(self):
        return messages.ACTION_DEPLOY_SUBSCRIPTION

    def get_failure_message(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return messages.ACTION_DEPLOY_SUBSCRIPTION_FAILED.format(subscription_name=subscription.name)

    def get_multithreading_properties(self):
        """Get how multithreading should be applied for that action

        :rtype: parallels.core.actions.utils.multithreading_properties.MultithreadingProperties
        """
        return MultithreadingProperties(can_use_threads=True)

    def run(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        existing_subscriptions_by_canonical_name = group_by_id(
            global_context.target_existing_objects.subscriptions, lambda s: s.name_canonical
        )

        existing_subscription = existing_subscriptions_by_canonical_name.get(subscription.name_canonical)
        if existing_subscription is not None:
            # subscription already exists
            if subscription.raw_dump.is_virtual_hosting and existing_subscription.hosting_type == 'none':
                # enable physical hosting
                logger.info(messages.ACTION_DEPLOY_SUBSCRIPTION_SET_PHYSICAL_HOSTING.format(
                    subscription_name=subscription.name
                ))
                global_context.hosting_repository.subscription.enable_virtual_hosting(subscription.model)
            client = subscription.model_client
            if client.login is not None:
                # set owner if needed
                global_context.hosting_repository.subscription.set_owner(subscription.model.name, client.login)
            self._apply_subscription_limits(global_context, subscription)
            return

        target_plesk_server = global_context.conn.target.plesk_server

        # Workaround for Plesk for Linux <= 17.0
        # parallel creation of subscription may fail (bugs PPPM-2705, PPPM-2734, PPP-16935)
        parallel_create_could_fail = (
            target_plesk_server.plesk_version < (17, 0) and not target_plesk_server.is_windows()
        )

        if parallel_create_could_fail:
            logger.debug(messages.PARALLEL_CREATION_SUBSCRIPTIONS_MAY_FAIL_ACQUIRE)
            self._lock.acquire()

        # invalidate cache associated with this subscription
        global_context.cache_state_controllers.subscription.invalidate_cache_states(subscription.name)

        try:
            warnings = global_context.hosting_repository.subscription.create(
                subscription.model,
                subscription.model_client,
                subscription.model_reseller,
                subscription.raw_dump.hosting_type != 'none'
            )
            if warnings:
                for warning in warnings:
                    global_context.safe.fail_subscription(
                        subscription.name, warning,
                        is_critical=False,
                        severity=Issue.SEVERITY_WARNING
                    )
        finally:
            if parallel_create_could_fail:
                logger.debug(messages.RELEASE_LOCK_FOR_CREATION_SUBSCRIPTIONS_OPERATION)
                self._lock.release()
        self._apply_subscription_limits(global_context, subscription)

    @staticmethod
    def _apply_subscription_limits(global_context, subscription):
        if subscription.model.plan_name is None or subscription.model.is_locked:
            # there are two possible cases: subscription not associated with hosting plan on target panel,
            # so it just created with default limits, or subscription is locked on source, so it limits
            # differ from limits in hosting plan;
            # apply subscription limits dumped from source panel to avoid limitations while deploying
            # of major business objects
            logger.info(messages.ACTION_DEPLOY_SUBSCRIPTION_SET_LIMITS.format(
                subscription_name=subscription.name
            ))
            # take limits from raw dump, not from converted dump,
            # to allow subscription creation when converted dump is not created yet,
            # in migration to Plesk Multi Server scenario
            limits = subscription.raw_dump.limits
            global_context.hosting_repository.subscription.set_limits(
                subscription.name,
                max_domains=limits.get('max_site'),
                max_subdomains=limits.get('max_subdom'),
                max_domain_aliases=limits.get('max_dom_aliases'),
                max_databases=limits.get('max_db'),
                max_mssql_databases=limits.get('max_mssql_db'),
                max_mail_accounts=limits.get('max_box'),
            )
