from collections import namedtuple

from .. import core
from parallels.core.utils.common import if_not_none
from parallels.core.utils.common.xml import text_elem, elem, seq, xml_bool

PlanInfo = namedtuple('PlanInfo', ('plan_id', 'name', 'owner_id', 'shell'))

PhysicalHostingDescriptor = namedtuple('PhysicalHostingDescriptor', (
    # list[PhysicalHostingProperty]
    'properties',
))

PhysicalHostingProperty = namedtuple('PhysicalHostingProperty', ('name',))

ServicePlanLimits = namedtuple('ServicePlanLimits', (
    'overuse', 
    'limits'  # list of ServicePlanLimit
))
ServicePlanLimit = namedtuple('ServicePlanLimit', ('name', 'value',))
ServicePlanPermission = namedtuple('ServicePlanPermission', ('name', 'value',))
ServicePlanPHPSetting = namedtuple('ServicePlanPHPSetting', ('name', 'value',))
ServicePlanHostingSetting = namedtuple('ServicePlanHostingSetting', ('name', 'value',))
ServicePlanLogRotation = namedtuple('ServicePlanLogRotation', (
    'status',  # bool
    'settings',  # ServicePlanLogRotationSettings
))
ServicePlanLogRotationSettings = namedtuple('ServicePlanLogRotationSettings', (
    'log_condition',  # ServicePlanLogRotationConditionBySize | ServicePlanLogRotationConditionByTime
    'log_max_num_files', 'log_compress', 'log_email'
))
ServicePlanLogRotationConditionBySize = namedtuple('ServicePlanLogRotationConditionBySize', (
    'size',  # size in Kb
))
ServicePlanLogRotationConditionByTime = namedtuple('ServicePlanLogRotationConditionByTime', (
    'time',  # Daily | Weekly | Monthly
))
ServicePlanMail = namedtuple('ServicePlanMail', (
    # ServicePlanMailNonexistentUserBounce | ServicePlanMailNonexistentUserForward |
    # ServicePlanMailNonexistentUserReject
    'nonexistent_user',
    # bool
    'webmail',
))
ServicePlanMailNonexistentUserBounce = namedtuple('ServicePlanMailNonexistentUserBounce', (
    'message'
))
ServicePlanMailNonexistentUserForward = namedtuple('ServicePlanMailNonexistentUserForward', (
    'address'
))
ServicePlanApsPackage = namedtuple('ServicePlanApsPackage', (
    # str | unicode
    'name',
    # str | unicode
    'value',
))


class ServicePlanMailNonexistentUserReject(object):
    pass

ServicePlanPreferences = namedtuple('ServicePlanPreferences', (
    # int
    'stat',
    # boolean
    'maillists',
    # boolean
    'mailservice',
    # str | unicode, 'master' | 'slave'
    'dns_zone_type',
))

ServicePlanWebServerSettings = namedtuple('ServicePlanWebServerSettings', (
    'additional', 'additional_ssl', 'additional_nginx', 'restrict_follow_symlinks', 'nginx_proxy_mode'
))


class ServicePlanOperator(object):
    FilterAll = core.FilterAll
    FilterById = core.declare_filter('FilterById', 'id')
    FilterByName = core.declare_filter('FilterByName', 'name')
    FilterByGuid = core.declare_filter('FilterByGuid', 'guid')

    class Get(core.operation_with_filter('Get', ('owner_id', 'owner_all',))):
        operator_name = 'service-plan'
        operation_name = 'get'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return seq(
                text_elem('owner-id', self.owner_id),
                elem('owner-all') if self.owner_all else None
            )

        @classmethod
        def parse(cls, elem):
            return [core.Result.parse(r, cls._parse_data) for r in elem.findall('result')]

        @classmethod
        def _get_value_by_name(cls, elem, path, name):
            for e in elem.findall(path):
                if e.findtext('name') == name:
                    return e.findtext('value')
            return None

        @classmethod
        def _parse_data(cls, elem):
            plan_id = int(elem.findtext('id'))
            name = elem.findtext('name')
            owner_id = if_not_none(elem.findtext('owner-id'), int)
            shell = cls._get_value_by_name(elem, './hosting/vrt_hst/property', 'shell')
            return PlanInfo(plan_id=plan_id, name=name, owner_id=owner_id, shell=shell)

    class Add(core.operation('Add', (
        'name', 'owner_login', 'limits', 'permissions', 'php_settings',
        'log_rotation', 'mail', 'preferences', 'hosting', 'web_server_settings',
    ))):
        operator_name = 'service-plan'
        operation_name = 'add'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def inner_xml(self):
            return seq(
                text_elem('name', self.name),
                text_elem('owner-login', self.owner_login),
                self._mail_xml(),
                self._limits_xml(),
                self._log_rotation_xml(),
                self._preferences_xml(),
                self._hosting_xml(),
                self._permissions_xml(),
                self._php_settings_xml(),
                self._web_server_settings_xml()
            )

        def _limits_xml(self):
            return elem('limits', seq(text_elem('overuse', self.limits.overuse)) + [
                elem('limit', [text_elem('name', limit.name), text_elem('value', limit.value)])
                for limit in self.limits.limits
            ])

        def _log_rotation_xml(self):
            if self.log_rotation is None:
                return None

            if self.log_rotation.status:
                items = []
                if isinstance(self.log_rotation.settings.log_condition, ServicePlanLogRotationConditionBySize):
                    log_condition_node = text_elem('log-bysize', self.log_rotation.settings.log_condition.size)
                elif isinstance(self.log_rotation.settings.log_condition, ServicePlanLogRotationConditionByTime):
                    log_condition_node = text_elem('log-bytime', self.log_rotation.settings.log_condition.time)
                else:
                    assert False

                items.append(elem('log-condition', [log_condition_node]))
                items.append(text_elem('log-max-num-files', self.log_rotation.settings.log_max_num_files))
                if self.log_rotation.settings.log_compress is not None:
                    items.append(text_elem('log-compress', xml_bool(self.log_rotation.settings.log_compress)))
                items.append(text_elem('log-email', self.log_rotation.settings.log_email))
                log_rotation_xml = elem('on', seq(*items))
            else:
                log_rotation_xml = elem('off')

            return elem('log-rotation', [log_rotation_xml])

        def _mail_xml(self):
            if self.mail is None:
                return None

            if self.mail.nonexistent_user is not None:
                if isinstance(self.mail.nonexistent_user, ServicePlanMailNonexistentUserBounce):
                    nonexistent = text_elem('bounce', self.mail.nonexistent_user.message)
                elif isinstance(self.mail.nonexistent_user, ServicePlanMailNonexistentUserForward):
                    nonexistent = text_elem('forward', self.mail.nonexistent_user.address)
                elif isinstance(self.mail.nonexistent_user, ServicePlanMailNonexistentUserReject):
                    nonexistent = elem('reject')
                else:
                    assert False
            else:
                nonexistent = None

            return elem('mail', seq(
                if_not_none(nonexistent, lambda e: elem('nonexistent-user', [e])),
                text_elem('webmail', self.mail.webmail),
            ))

        def _preferences_xml(self):
            if self.preferences is None:
                return None

            return elem('preferences', seq(
                # TODO: Validate domain stat ttl: it should be greater than or equal the default server value
                # skip it before such validation will be implemented: plesk unable to create plan,
                # if specified domain stat ttl less than default server value
                # text_elem('stat', self.preferences.stat),
                text_elem('maillists', if_not_none(self.preferences.maillists, xml_bool)),
                text_elem('mailservice', if_not_none(self.preferences.mailservice, xml_bool)),
                text_elem('dns_zone_type', self.preferences.dns_zone_type)
            ))

        def _hosting_xml(self):
            if self.hosting is None:
                return None

            return elem('hosting', seq(*[
                elem('property', [text_elem('name', prop.name), text_elem('value', prop.value)])
                for prop in self.hosting
            ]))

        def _permissions_xml(self):
            return elem('permissions', [
                elem('permission', [text_elem('name', permission.name), text_elem('value', permission.value)])
                for permission in self.permissions
            ])

        def _php_settings_xml(self):
            if self.php_settings is None:
                return None

            return elem(
                'php-settings', [
                    elem('setting', [text_elem('name', setting.name), text_elem('value', setting.value)])
                    for setting in self.php_settings
                ]
            )

        def _web_server_settings_xml(self):
            if self.web_server_settings is None:
                return None

            return elem('web-server-settings', seq(
                text_elem('additional', self.web_server_settings.additional),
                text_elem('additional-ssl', self.web_server_settings.additional_ssl),
                text_elem('additional-nginx', self.web_server_settings.additional_nginx),
                text_elem(
                    'restrict-follow-sym-links',
                    xml_bool(self.web_server_settings.restrict_follow_symlinks)
                ),
                text_elem(
                    'nginx-proxy-mode',
                    xml_bool(self.web_server_settings.nginx_proxy_mode)
                ),
            ))

        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'))

    class GetPhysicalHostingDescriptor(core.operation_with_filter('GetPhysicalHostingDescriptor')):
        operator_name = 'service-plan'
        operation_name = 'get-physical-hosting-descriptor'
        min_api_version = '1.6.3.0'
        max_api_version = None

        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'), cls._parse_data)

        @classmethod
        def _parse_data(cls, elem):
            return PhysicalHostingDescriptor(
                properties=[
                    PhysicalHostingProperty(prop_node.findtext('name'))
                    for prop_node in elem.findall('descriptor/property')
                ]
            )

    class GetPermissionDescriptor(core.operation_with_filter('GetPermissionDescriptor')):
        operator_name = 'service-plan'
        operation_name = 'get-permission-descriptor'
        min_api_version = '1.6.3.0'
        max_api_version = None

        @classmethod
        def parse(cls, command_node):
            """
            :type command_node: xml.etree.ElementTree.Element
            :rtype: parallels.plesk.utils.xml_rpc.plesk.core.Success|parallels.plesk.utils.xml_rpc.plesk.core.Failure
            """
            return core.Result.parse(command_node.find('result'), cls._parse_data)

        @classmethod
        def _parse_data(cls, result_node):
            """
            :type result_node: xml.etree.ElementTree.Element
            :rtype: parallels.plesk.utils.xml_rpc.plesk.core.Descriptor
            """
            return core.Descriptor.parse(result_node.find('descriptor'))

    class GetLimitDescriptor(core.operation_with_filter('GetLimitDescriptor')):
        operator_name = 'service-plan'
        operation_name = 'get-limit-descriptor'
        min_api_version = '1.6.3.0'
        max_api_version = None

        @classmethod
        def parse(cls, command_node):
            """
            :type command_node: xml.etree.ElementTree.Element
            :rtype: parallels.plesk.utils.xml_rpc.plesk.core.Success|parallels.plesk.utils.xml_rpc.plesk.core.Failure
            """
            return core.Result.parse(command_node.find('result'), cls._parse_data)

        @classmethod
        def _parse_data(cls, result_node):
            """
            :type result_node: xml.etree.ElementTree.Element
            :rtype: parallels.plesk.utils.xml_rpc.plesk.core.Descriptor
            """
            return core.Descriptor.parse(result_node.find('descriptor'))

    class EnableApsFilter(core.operation_with_filter('EnableApsFilter', ('owner_login',))):
        operator_name = 'service-plan'
        operation_name = 'enable-aps-filter'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return seq(text_elem('owner-login', self.owner_login))

        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'))

    class AddPackage(core.operation_with_filter('AddPackage', ('owner_login', 'packages',))):
        operator_name = 'service-plan'
        operation_name = 'add-package'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return seq(text_elem('owner-login', self.owner_login)) + [
                elem('package', [
                    text_elem('name', package.name),
                    text_elem('value', package.value)
                ])
                for package in self.packages
            ]

        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'))
