from parallels.core.converter.business_objects.plans import HostingPlanAdapter
from parallels.core.reports.model.issue import Issue
from parallels.core.utils.base64_utils import base64_decode
from parallels.core.utils.common import if_not_none, compare_by
from parallels.core.utils.plesk_limits_permissions import HOSTING_PLAN_LIMIT_NAMES
from parallels.core.utils.plesk_utils import PHPHandler
from parallels.core.utils.webmail import select_target_webmail
from parallels.plesk import messages
from parallels.plesk.models.target_data_model import PleskPlanSettings, HostingPlanLogRotation, \
    HostingPlanLogRotationSettings, HostingPlanLogRotationConditionByTime, HostingPlanLogRotationConditionBySize, \
    HostingPlanMail, HostingPlanMailNonexistentUserBounce, HostingPlanMailNonexistentUserForward, \
    HostingPlanMailNonexistentUserReject, HostingPlanPreferences, PleskPlanHostingSetting, \
    HostingPlanWebServerSettings, HostingPlanApsBundleFilter, HostingPlanApsBundleFilterItem, HostingPlanCustomItem, \
    HostingPlanDefaultDatabaseServer


class PleskHostingPlanAdapter(HostingPlanAdapter):
    def __init__(
        self, allowed_limits, allowed_permissions, allowed_hosting_settings, allowed_php_handlers,
        is_windows=False, remote_smarter_mail=False, allowed_webmails=None
    ):
        """
        :type allowed_limits: set[str | unicode]
        :type allowed_permissions: set[str | unicode]
        :type allowed_hosting_settings: set[str | unicode]
        :type allowed_php_handlers: list[parallels.core.utils.plesk_utils.PHPHandler] | None
        :type is_windows: bool
        :type remote_smarter_mail: bool
        :type allowed_webmails: list[str | unicode] | None
        """
        self._allowed_limits = allowed_limits
        self._allowed_permissions = allowed_permissions
        self._allowed_hosting_settings = allowed_hosting_settings
        self._allowed_php_handlers = allowed_php_handlers
        self._allowed_webmails = allowed_webmails
        self._is_windows = is_windows
        self._remote_smarter_mail = remote_smarter_mail

    def convert(self, backup_plan):
        """Convert plan settings from Plesk backup to format ready to import with Plesk API

        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: (parallels.plesk.models.target_data_model.PleskPlanSettings, list[parallels.core.reports.model.issue.Issue])
        """
        issues = []
        hosting = self._convert_hosting(
            backup_plan, self._allowed_hosting_settings, self._allowed_php_handlers, issues
        )
        return (
            PleskPlanSettings(
                overuse=self._convert_overuse(backup_plan),
                limits=self._convert_limits(backup_plan, self._allowed_limits),
                permissions=self._convert_permissions(backup_plan, self._allowed_permissions),
                php_settings_node=backup_plan.php_settings_node,
                log_rotation=self._convert_log_rotation(backup_plan),
                mail=self._convert_mail(backup_plan),
                preferences=self._convert_preferences(backup_plan),
                hosting=hosting,
                virtual_hosting_enabled=backup_plan.properties.get('vh_type', 'physical') != 'none',
                web_server_settings=self._convert_web_server_settings(backup_plan),
                aps_bundle_filter=self._convert_aps_bundle_filter(backup_plan),
                custom_plan_items=self._convert_custom_plan_items(backup_plan),
                default_db_servers=self._convert_default_database_servers(backup_plan)
            ),
            issues
        )

    @staticmethod
    def _convert_overuse(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: str | unicode
        """
        return backup_plan.properties.get('overuse')

    @staticmethod
    def _convert_limits(backup_plan, allowed_limits):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :type allowed_limits: set[str | unicode]
        :rtype: dict[string, string]
        """
        return {
            limit_name: backup_plan.properties[limit_name]
            for limit_name in allowed_limits & set(HOSTING_PLAN_LIMIT_NAMES)
            if limit_name in backup_plan.properties
        }

    @staticmethod
    def _convert_permissions(backup_plan, allowed_permissions):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :type allowed_permissions: set[str | unicode]
        :rtype: dict[string, string]
        """
        return {
            permission_name: backup_plan.properties[permission_name]
            for permission_name in allowed_permissions if permission_name in backup_plan.properties
        }

    @staticmethod
    def _convert_log_rotation(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: parallels.plesk.models.target_data_model.HostingPlanLogRotationSettings
        """
        if backup_plan.log_rotation is not None:
            if backup_plan.log_rotation.period is not None:
                log_condition = HostingPlanLogRotationConditionByTime(backup_plan.log_rotation.period.capitalize())
            else:
                log_condition = HostingPlanLogRotationConditionBySize(backup_plan.log_rotation.max_size)

            log_rotation = HostingPlanLogRotation(
                status=backup_plan.log_rotation.enabled,
                settings=HostingPlanLogRotationSettings(
                    log_condition=log_condition,
                    log_max_num_files=backup_plan.log_rotation.max_logfiles_number,
                    log_compress=backup_plan.log_rotation.compress,
                    log_email=backup_plan.log_rotation.email,
                )
            )
        else:
            log_rotation = None
        return log_rotation

    def _convert_mail(self, backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: parallels.plesk.models.target_data_model.HostingPlanMail
        """
        source_webmail = backup_plan.properties.get('webmail')
        target_webmail = select_target_webmail(source_webmail, self._allowed_webmails, self._remote_smarter_mail)

        return HostingPlanMail(
            nonexistent_user=self._convert_mail_nonexistent_user(backup_plan),
            webmail=target_webmail,
        )

    @staticmethod
    def _convert_mail_nonexistent_user(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: None |
            parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserBounce |
            parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserForward |
            parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserReject
        """
        nonexistent_prop = backup_plan.properties.get('nonexist_mail')
        if nonexistent_prop == 'bounce':
            nonexistent_user = HostingPlanMailNonexistentUserBounce(message=backup_plan.properties.get('bounce_mess'))
        elif nonexistent_prop == 'catch':
            nonexistent_user = HostingPlanMailNonexistentUserForward(address=backup_plan.properties.get('catch_addr'))
        elif nonexistent_prop == 'reject':
            nonexistent_user = HostingPlanMailNonexistentUserReject()
        else:
            nonexistent_user = None
        return nonexistent_user

    @staticmethod
    def _convert_preferences(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: parallels.plesk.models.target_data_model.HostingPlanPreferences
        """
        return HostingPlanPreferences(
            stat=backup_plan.properties.get('stat_ttl', '3'),
            maillists=backup_plan.properties.get('maillists', 'false') == 'true',
            mailservice=backup_plan.properties.get('mailservice', 'true') == 'true',
            dns_zone_type=backup_plan.properties.get('dns_type', 'master')
        )

    @classmethod
    def _convert_hosting(cls, backup_plan, allowed_hosting_settings, allowed_php_handlers, issues):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :type allowed_hosting_settings: set[str | unicode]
        :type allowed_php_handlers: list[parallels.core.utils.plesk_utils.PHPHandler] | None
        :type issues: list
        :rtype: list[parallels.plesk.models.target_data_model.PleskPlanHostingSetting]
        """
        available_hostings_list = set(allowed_hosting_settings)
        if backup_plan.properties.get('php') != 'true':
            available_hostings_list -= {'php_handler_type', 'php_handler_id'}
        hostings = []

        if 'vh_type' in backup_plan.properties and backup_plan.properties['vh_type'] == 'none':
            return hostings

        for name, value in backup_plan.properties.items():
            if value is None:
                continue
                
            if name == 'quota':
                name = 'ftp_quota'

            if name == 'managed_runtime_version' and backup_plan.properties.get('asp_dot_net') == 'false':
                # we should not set ASP.NET version if ASP.NET is disabled, otherwise Plesk fails to create plan
                continue

            if name == 'php_handler_id' and allowed_php_handlers is not None:
                requested_php_handler = PHPHandler(
                    handler_id=value,
                    version=backup_plan.properties.get('php_version'),
                    handler_type=backup_plan.properties.get('php_handler_type'),
                    full_version=None
                )

                if len(allowed_php_handlers) == 0:
                    issues.append(
                        Issue(
                            'service-template-php-handler', Issue.SEVERITY_WARNING,
                            messages.SERVICE_PLAN_PHP_HANDLER_NO_HANDLERS_AVAILABLE_ISSUE.format(
                                plan_name=backup_plan.name,
                                source_php_handler=requested_php_handler.pretty_str(),
                            ),
                            messages.SERVICE_PLAN_PHP_HANDLER_NO_HANDLERS_AVAILABLE_SOLUTION
                        )
                    )
                    continue

                # We do not check web server that handles PHP here (Apache or nginx), just check version, type and ID
                # Web server compatibility issue should not be common case here, but it is still possible,
                # so probably we need to implement such check in the future.

                criteria = ['version', 'handler_type', 'handler_id']
                selected_php_handler = cls._select_php_handler(
                    requested_php_handler, allowed_php_handlers, criteria
                )
                if not compare_by(selected_php_handler, requested_php_handler, criteria):
                    value = selected_php_handler.handler_id
                    issues.append(
                        Issue(
                            'service-template-php-handler', Issue.SEVERITY_WARNING,
                            messages.SERVICE_PLAN_PHP_HANDLER_NOT_AVAILABLE_ISSUE.format(
                                plan_name=backup_plan.name,
                                source_php_handler=requested_php_handler.pretty_str(),
                                target_php_handler=selected_php_handler.pretty_str()
                            ),
                            messages.SERVICE_PLAN_PHP_HANDLER_NOT_AVAILABLE_SOLUTION
                        )
                    )

            if name in available_hostings_list:
                hostings.append(PleskPlanHostingSetting(name=name, value=value))

        return hostings

    @staticmethod
    def _select_php_handler(required_handler, allowed_php_handlers, criteria):
        for i in range(len(criteria), 0, -1):
            for handler in allowed_php_handlers:
                if all(getattr(handler, attrib) == getattr(required_handler, attrib) for attrib in criteria[:i]):
                    return handler

        # If no suitable PHP handler exists - simply select the first one
        return allowed_php_handlers[0]

    @staticmethod
    def _convert_web_server_settings(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: parallels.plesk.models.target_data_model.HostingPlanWebServerSettings
        """
        return HostingPlanWebServerSettings(
            additional=if_not_none(backup_plan.properties.get('additionalSettings'), base64_decode),
            additional_ssl=if_not_none(backup_plan.properties.get('additionalSslSettings'), base64_decode),
            additional_nginx=if_not_none(backup_plan.properties.get('additionalNginxSettings'), base64_decode),
            restrict_follow_symlinks=if_not_none(backup_plan.properties.get('restrictFollowSymLinks'), base64_decode),
            nginx_proxy_mode=if_not_none(backup_plan.properties.get('nginxProxyMode'), base64_decode)
        )

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

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

    @staticmethod
    def _convert_custom_plan_items(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: list[parallels.plesk.models.target_data_model.HostingPlanCustomItem]
        """
        return [
            HostingPlanCustomItem(
                name=item.name, guid=item.guid, visible=item.visible,
                hint=item.hint, description=item.description,
                url=item.url, options=item.options
            )
            for item in backup_plan.custom_plan_items
        ]

    @staticmethod
    def _convert_default_database_servers(backup_plan):
        """
        :type backup_plan: parallels.core.dump.data_model.HostingPlan
        :rtype: list[parallels.plesk.models.target_data_model.HostingPlanDefaultDatabaseServer]
        """
        return [
            HostingPlanDefaultDatabaseServer(
                type=server.type, host=server.host, port=server.port
            )
            for server in backup_plan.default_db_servers
        ]
