import re
from parallels.core.utils.common import format_list, exists, is_ascii_string, is_empty
from parallels.core.utils.common.ip import is_ipv4, is_ipv6
from parallels.plesk import messages
from parallels.plesk.hosting_description.model import ForwardingType


class HostingDescriptionAttributesValidator(object):
    def validate_model(self, model, database_servers=None):
        """
        :type model: parallels.plesk.hosting_description.model.HostingDescriptionModel
        :type database_servers: list[parallels.custom_panel_migrator.connections.DatabaseServerConfig]
        """
        errors = []
        self._validate_subdomains(errors, model)
        self._validate_mailboxes(errors, model)
        self._validate_databases(errors, model, database_servers)
        self._validate_document_roots(errors, model)
        self._validate_forwarding(errors, model)
        self._validate_hosting_types(errors, model)
        self._validate_hosting_type_with_sites(errors, model)
        return errors

    def _validate_document_roots(self, errors, model):
        for subscription in model.iter_all_subscriptions():
            self._validate_site_document_root(
                errors, subscription, messages.SUBSCRIPTION_TITLE.format(subscription=subscription.name)
            )
            for addon_domain in subscription.iter_addon_domains():
                self._validate_site_document_root(
                    errors, addon_domain, messages.ADDON_DOMAIN_TITLE.format(
                        subscription=subscription.name, addon_domain=addon_domain.name
                    )
                )
            for subdomain in subscription.iter_subdomains():
                self._validate_site_document_root(
                    errors, subdomain, messages.SUBDOMAIN_TITLE.format(
                        subscription=subscription.name, subdomain=subdomain.name
                    )
                )

    def _validate_forwarding(self, errors, model):
        for subscription in model.iter_all_subscriptions():
            self._validate_site_forwarding(
                errors, subscription, messages.SUBSCRIPTION_TITLE.format(subscription=subscription.name)
            )
            for addon_domain in subscription.iter_addon_domains():
                self._validate_site_forwarding(
                    errors, addon_domain, messages.ADDON_DOMAIN_TITLE.format(
                        subscription=subscription.name, addon_domain=addon_domain.name
                    )
                )
            for subdomain in subscription.iter_subdomains():
                self._validate_site_forwarding(
                    errors, subdomain, messages.SUBDOMAIN_TITLE.format(
                        subscription=subscription.name, subdomain=subdomain.name
                    )
                )

    def _validate_hosting_types(self, errors, model):
        for subscription in model.iter_all_subscriptions():
            self._validate_site_hosting_types(
                errors, subscription, messages.SUBSCRIPTION_TITLE.format(subscription=subscription.name)
            )
            for addon_domain in subscription.iter_addon_domains():
                self._validate_site_hosting_types(
                    errors, addon_domain, messages.ADDON_DOMAIN_TITLE.format(
                        subscription=subscription.name, addon_domain=addon_domain.name
                    )
                )
            for subdomain in subscription.iter_subdomains():
                self._validate_site_hosting_types(
                    errors, subdomain, messages.SUBDOMAIN_TITLE.format(
                        subscription=subscription.name, subdomain=subdomain.name
                    )
                )

    @staticmethod
    def _validate_site_document_root(errors, domain, domain_description):
        if domain.target_document_root is None:
            return

        if not is_ascii_string(domain.target_document_root):
            errors.append(
                messages.VALIDATION_NON_ASCII_DOCUMENT_ROOT.format(
                    domain_description=domain_description,
                    document_root=domain.target_document_root
                )
            )

    @staticmethod
    def _validate_site_forwarding(errors, domain, domain_description):
        if domain.forwarding_url is None and domain.forwarding_type is None:
            return

        allowed_values = [
            ForwardingType.MOVED_PERMANENTLY, ForwardingType.MOVED_TEMPORARILY, ForwardingType.FRAME_FORWARDING
        ]

        if domain.forwarding_type is not None and domain.forwarding_type not in allowed_values:
            errors.append(
                messages.VALIDATION_INVALID_FORWARDING_TYPE.format(
                    domain_description=domain_description,
                    forwarding_type=domain.forwarding_type,
                    allowed_values=format_list(allowed_values)
                )
            )
        elif domain.forwarding_type is not None and is_empty(domain.forwarding_url):
            errors.append(
                messages.VALIDATION_FORWARDING_TYPE_WITH_NO_URL.format(
                    domain_description=domain_description,
                )
            )

    @staticmethod
    def _validate_site_hosting_types(errors, domain, domain_description):
        hostings_specified = [
            domain.no_web_hosting,
            (domain.forwarding_type is not None or domain.forwarding_url is not None),
            domain.target_document_root
        ]
        if len([h for h in hostings_specified if h]) > 1:
            errors.append(
                messages.MULTIPLE_HOSTINGS_SPECIFIED.format(
                    domain_description=domain_description,
                )
            )

    @staticmethod
    def _validate_hosting_type_with_sites(errors, model):
        for subscription in model.iter_all_subscriptions():
            sites = (
                [addon.name for addon in subscription.iter_addon_domains()] +
                [subdomain.name for subdomain in subscription.iter_subdomains()]
            )
            if subscription.forwarding_type is not None or subscription.forwarding_url is not None:
                if len(sites) > 0:
                    errors.append(messages.FORWARDING_SUBSCRIPTION_WITH_SITES.format(
                        subscription=subscription.name, sites=format_list(sites)
                    ))
            if subscription.no_web_hosting:
                if len(sites) > 0:
                    errors.append(messages.NO_HOSTING_SUBSCRIPTION_WITH_SITES.format(
                        subscription=subscription.name, sites=format_list(sites)
                    ))

    def _validate_subdomains(self, errors, model):
        for subscription in model.iter_all_subscriptions():
            all_domain_names = set([subscription.name] + [addon.name for addon in subscription.iter_addon_domains()])
            for subdomain in subscription.iter_subdomains():
                self._validate_subdomain(all_domain_names, errors, subdomain, subscription)

    @staticmethod
    def _validate_subdomain(all_domain_names, errors, subdomain, subscription):
        if subdomain.parent_domain is not None:
            if subdomain.parent_domain not in all_domain_names:
                errors.append(messages.VALIDATION_NO_SUCH_PARENT_DOMAIN.format(
                    subscription=subscription.name, subdomain=subdomain.name,
                    parent_domain=subdomain.parent_domain, list_of_domains=format_list(all_domain_names)
                ))
        else:
            has_parent_domain = any(
                subdomain.name.endswith('.%s' % domain_name) for domain_name in all_domain_names
            )
            if not has_parent_domain:
                errors.append(messages.VALIDATION_PARENT_DOMAIN_NOT_FOUND.format(
                    subscription=subscription.name, subdomain=subdomain.name,
                    list_of_domains=format_list(all_domain_names)
                ))

    def _validate_mailboxes(self, errors, model):
        for subscription in model.iter_all_subscriptions():
            if subscription.mail_service is not None:
                if subscription.mail_service.mail_ip is not None:
                    if not is_ipv4(subscription.mail_service.mail_ip):
                        errors.append(messages.VALIDATION_INVALID_IPV4_ADDRESS.format(
                            subscription=subscription.name, ip=subscription.mail_service.mail_ip
                        ))

                if subscription.mail_service.mail_ipv6 is not None:
                    if not is_ipv6(subscription.mail_service.mail_ipv6):
                        errors.append(messages.VALIDATION_INVALID_IPV6_ADDRESS.format(
                            subscription=subscription.name, ip=subscription.mail_service.mail_ipv6
                        ))

            for mailbox in subscription.iter_mailboxes():
                self._validate_mailbox_name(errors, mailbox, subscription)
                self._validate_mailbox_quota(errors, mailbox, subscription)

    @staticmethod
    def _validate_mailbox_name(errors, mailbox, subscription):
        if '@' in mailbox.short_name:
            errors.append(messages.VALIDATION_INVALID_MAILBOX_NAME.format(
                subscription=subscription.name, mailbox=mailbox.short_name
            ))
            return

    @staticmethod
    def _validate_mailbox_quota(errors, mailbox, subscription):
        if mailbox.quota is None or mailbox.quota == '-1':
            return

        m = re.match('^(\d+)\s*(M|K|)$', mailbox.quota)
        if m is None:
            errors.append(messages.VALIDATION_INVALID_MAILBOX_LIMIT.format(
                subscription=subscription.name, mailbox=mailbox.name, limit=mailbox.quota
            ))

    def _validate_databases(self, errors, model, database_servers):
        """
        :type database_servers: list[parallels.custom_panel_migrator.connections.DatabaseServerConfig]
        """
        for subscription in model.iter_all_subscriptions():
            for database in subscription.iter_databases():
                self._validate_database(database, errors, subscription, database_servers)

    @staticmethod
    def _validate_database(database, errors, subscription, database_servers):
        """
        :type database_servers: list[parallels.custom_panel_migrator.connections.DatabaseServerConfig]
        """
        if database.db_type not in ('mysql', 'mssql', 'postgresql'):
            errors.append(messages.VALIDATION_INVALID_DATABASE_TYPE.format(
                subscription=subscription.name, database=database.name,
                database_type=database.db_type
            ))
            return

        if database.server_id is not None:
            if database_servers is None or not exists(database_servers, lambda s: s.db_server_id == database.server_id):
                errors.append(messages.VALIDATE_NO_SUCH_DATABASE_SERVER.format(
                    subscription=subscription.name, database=database.name,
                    db_server_id=database.server_id
                ))
