from collections import defaultdict

from parallels.core.logging import get_logger
from parallels.core import target_data_model
from parallels.core.converter.business_objects.common import SOURCE_TARGET
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.common import exists
from parallels.plesk import messages
from parallels.plesk.models.target_data_model import  AdminResellerPlanApsBundleFilterItem, \
    AdminResellerPlanApsBundleFilter

logger = get_logger(__name__)


class ResellerPlanConverter(object):
    """Convert and merge admin's reseller plans from source and target panels"""

    def convert_plans(self, source_panels, target_plans, plan_names, allowed_limits, allowed_permissions, report):
        """Convert admin's reseller plans, merge plans within sources and target.

        Return list of plans expected to have on target system.

        :type source_panels: list[parallels.core.global_context.SourceInfo]
        :type target_plans: list[parallels.plesk.utils.xml_rpc.plesk.operator.reseller_plan.ResellerPlanInfo]
        :type plan_names: list[str | unicode]
        :type allowed_limits: set[str | unicode]
        :type allowed_permissions: set[str | unicode]
        :type report: parallels.core.reports.model.report.Report
        :rtype: list[parallels.core.target_data_model.ResellerPlan]
        """
        logger.debug(messages.LOG_CONVERT_ADMIN_RESELLER_PLANS)
        all_converted_plans = defaultdict(list)
        logger.debug(messages.LOG_CONVERT_TARGET_ADMIN_RESELLER_PLANS)
        self._create_target_panel_plans(all_converted_plans, target_plans)
        logger.debug(messages.LOG_CONVERT_SOURCE_PANEL_ADMIN_RESELLER_PLANS)
        self._create_source_panel_plans(
            all_converted_plans, plan_names, allowed_limits, allowed_permissions, source_panels
        )
        logger.debug(messages.LOG_MERGE_ADMIN_RESELLER_PLANS)
        merged_plans = self._merge_plans(all_converted_plans, plan_names, report)

        return merged_plans

    @staticmethod
    def _create_target_panel_plans(all_converted_plans, target_plans):
        """Create converted plans from target panel plans, add them to all converted plans dictionary

        :type all_converted_plans: defaultdict
        :type target_plans: list[parallels.plesk.utils.xml_rpc.plesk.operator.reseller_plan.ResellerPlanInfo]
        :rtype: None
        """
        for target_plan in target_plans:
            all_converted_plans[target_plan.name].append(
                target_data_model.ResellerPlan(
                    name=target_plan.name,
                    oversell=None, overuse=None, limits=None, permissions=None, aps_bundle_filter=None,
                    source=SOURCE_TARGET,
                )
            )

    def _create_source_panel_plans(
        self, all_converted_plans, plan_names, allowed_limits, allowed_permissions, source_panels
    ):
        """Create converted plans from source panel plans, add them to all converted plans dictionary

        :type all_converted_plans: defaultdict
        :type plan_names: list[str | unicode]
        :type source_panels: list[parallels.core.global_context.SourceInfo]
        :rtype: None
        """
        for source in source_panels:
            backup = source.load_raw_dump()
            for backup_plan in backup.iter_admin_reseller_plans():
                if backup_plan.name in plan_names:
                    converted_source_plan = target_data_model.ResellerPlan(
                        name=backup_plan.name,
                        oversell=backup_plan.properties.get('oversell'),
                        overuse=backup_plan.properties.get('overuse'),
                        limits={
                            limit_name: backup_plan.properties[limit_name]
                            for limit_name in allowed_limits
                            if limit_name in backup_plan.properties
                        },
                        permissions={
                            permission_name: backup_plan.properties[permission_name]
                            for permission_name in allowed_permissions
                            if permission_name in backup_plan.properties
                        },
                        aps_bundle_filter=self._convert_aps_bundle_filter(backup_plan),
                        source=source.id,
                    )
                    all_converted_plans[backup_plan.name].append(converted_source_plan)

    @staticmethod
    def _convert_aps_bundle_filter(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.ResellerPlan
        :rtype: parallels.plesk.models.target_data_model.AdminResellerPlanApsBundleFilter | None
        """
        if backup_plan.aps_filter is None:
            return None

        return AdminResellerPlanApsBundleFilter(
            type=backup_plan.aps_filter.type,
            items=[
                AdminResellerPlanApsBundleFilterItem(name=item.name, value=item.value)
                for item in backup_plan.aps_filter.items
            ]
        )

    @staticmethod
    def _merge_plans(all_converted_plans, plan_names, report):
        """Merge converted plans to get single converted plan for each plan name

        :type all_converted_plans: defaultdict
        :type plan_names: list[str | unicode]
        :type report: parallels.core.reports.model.report.Report
        :rtype: list[parallels.plesk.models.target_data_model.AdminResellerPlan]
        """
        merged_plans = []
        for plan_name in plan_names:
            converted_plans = all_converted_plans.get(plan_name, [])
            if len(converted_plans) == 0:
                report.add_issue(
                    'no_such_reseller_plan', Issue.SEVERITY_ERROR,
                    messages.NO_SUCH_ADMIN_RESELLER_PLAN_ISSUE % plan_name,
                    messages.NO_SUCH_ADMIN_RESELLER_PLAN_SOLUTION
                )
            else:
                first_converted_plan = converted_plans[0]
                source_panels = [p.source for p in converted_plans if p.source != SOURCE_TARGET]
                exists_on_target = exists(converted_plans, lambda pl: pl.source == SOURCE_TARGET)
                if len(source_panels) > 1 and not exists_on_target:
                    report.add_issue(
                        'reseller_plan_on_multiple_source', Issue.SEVERITY_WARNING,
                        messages.ADMIN_RESELLER_PLAN_ON_MULTIPLE_SOURCES_ISSUE % (
                            plan_name, source_panels[0]
                        )
                    )
                if exists_on_target:
                    logger.debug(
                        messages.LOG_RESELLER_ADMIN_PLAN_ALREADY_EXISTS,
                        plan_name
                    )

                merged_plans.append(first_converted_plan)

        return merged_plans
