from itertools import chain
from datetime import datetime

from parallels.core.utils.common import if_not_none
from parallels.core.utils.entity import Entity

from .. import core
from parallels.core.utils.common.xml import elem, text_elem, seq
from parallels.core.utils.common.ip import is_ipv4, is_ipv6


class Ips(Entity):
    def __init__(self, v4, v6):
        self._v4 = v4
        self._v6 = v6

    @property
    def v4(self):
        return self._v4

    @property
    def v6(self):
        return self._v6


def _list_ips(ips):
    ip_addresses_list = []
    if ips.v4 is not None:
        ip_addresses_list.append(ips.v4)
    if ips.v6 is not None:
        ip_addresses_list.append(ips.v6)
    return ip_addresses_list


# copy from plesks-migrator/parallels/plesks_migrator/data_model.py
# until we decide where to put this Plesk-specific code
def _parse_plesk_ips(ips):
    ipv4 = []
    ipv6 = []
    for ip in ips:
        if is_ipv4(ip):
            ipv4.append(ip)
        elif is_ipv6(ip):
            ipv6.append(ip)
        else:
            raise Exception(u"Unknown IP address type: %s" % ip)

    assert 0 <= len(ipv4) <= 1
    assert 0 <= len(ipv6) <= 1

    return Ips(
        v4=ipv4[0] if len(ipv4) != 0 else None,
        v6=ipv6[0] if len(ipv6) != 0 else None,
    )


class SubscriptionInfo(Entity):
    def __init__(self, gen_info, hosting, mail, limits, subscriptions):
        self._gen_info = gen_info
        self._hosting = hosting
        self._mail = mail
        self._limits = limits
        self._subscriptions = subscriptions

    @property
    def gen_info(self):
        return self._gen_info

    @property
    def hosting(self):
        return self._hosting

    @property
    def mail(self):
        return self._mail

    @property
    def limits(self):
        return self._limits

    @property
    def subscriptions(self):
        return self._subscriptions


class SubscriptionGenInfo(Entity):
    def __init__(
        self, creation_date, name, ascii_name, status, real_size, owner_id, owner_login,
        dns_ip_address, htype, guid, vendor_guid, external_id, sb_site_uuid
    ):
        self._creation_date = creation_date
        self._name = name
        self._ascii_name = ascii_name
        self._status = status
        self._real_size = real_size
        self._owner_id = owner_id
        self._owner_login = owner_login
        self._dns_ip_address = dns_ip_address
        self._htype = htype
        self._guid = guid
        self._vendor_guid = vendor_guid
        self._external_id = external_id
        self._sb_site_uuid = sb_site_uuid

    @property
    def creation_date(self):
        return self._creation_date

    @property
    def name(self):
        return self._name

    @property
    def ascii_name(self):
        return self._ascii_name

    @property
    def status(self):
        return self._status

    @property
    def real_size(self):
        return self._real_size

    @property
    def owner_id(self):
        return self._owner_id

    @property
    def owner_login(self):
        return self._owner_login

    @property
    def dns_ip_address(self):
        return self._dns_ip_address

    @property
    def htype(self):
        return self._htype

    @property
    def guid(self):
        return self._guid

    @property
    def vendor_guid(self):
        return self._vendor_guid

    @property
    def external_id(self):
        return self._external_id

    @property
    def sb_site_uuid(self):
        return self._sb_site_uuid


class SubscriptionSubscriptions(Entity):
    def __init__(self, subscriptions):
        # list of SubscriptionSubscription
        self._subscriptions = subscriptions

    @property
    def subscriptions(self):
        return self._subscriptions


class SubscriptionSubscription(Entity):
    def __init__(self, plan):
        # instance of SubscriptionSubscriptionPlan
        self._plan = plan

    @property
    def plan(self):
        return self._plan


class SubscriptionSubscriptionPlan(Entity):
    def __init__(self, guid):
        self._guid = guid

    @property
    def guid(self):
        return self._guid


class LimitsInfo(Entity):
    def __init__(self, overuse, limits):
        self._overuse = overuse
        self._limits = limits

    @property
    def overuse(self):
        return self._overuse

    @property
    def limits(self):
        return self._limits

    def outer_xml(self):
        return elem(
            'limits',
            seq(text_elem('overuse', self.overuse)) + 
            [
                elem('limit', [text_elem('name', name), text_elem('value', value)]) 
                for name, value in self.limits.items()
            ] if self.limits is not None else []
        )


class MailPreferences(Entity):
    def __init__(self, ip_addresses):
        self._ip_addresses = ip_addresses

    @property
    def ip_addresses(self):
        return self._ip_addresses


class SubscriptionHostingNone(Entity):
    @staticmethod
    def outer_xml():
        return elem('none')


class SubscriptionHostingStandardForwarding(Entity):
    def __init__(self, dest_url, ip_addresses):
        self._dest_url = dest_url
        self._ip_addresses = ip_addresses

    @property
    def dest_url(self):
        return self._dest_url

    @property
    def ip_addresses(self):
        return self._ip_addresses

    def outer_xml(self):
        return elem('std_fwd', chain(
            [text_elem('dest_url', self.dest_url)],
            [text_elem('ip_address', ip_address) for ip_address in _list_ips(self.ip_addresses)]
        ))
            

class SubscriptionHostingFrameForwarding(Entity):
    def __init__(self, dest_url, ip_addresses):
        self._dest_url = dest_url
        self._ip_addresses = ip_addresses

    @property
    def dest_url(self):
        return self._dest_url

    @property
    def ip_addresses(self):
        return self._ip_addresses

    def outer_xml(self):
        return elem('frm_fwd', chain( 
            [text_elem('dest_url', self.dest_url)],
            [text_elem('ip_address', ip_address) for ip_address in _list_ips(self.ip_addresses)]
        ))


class SubscriptionHostingVirtual(Entity):
    def __init__(self, properties, ip_addresses):
        self._properties = properties
        self._ip_addresses = ip_addresses

    @property
    def properties(self):
        return self._properties

    @property
    def ip_addresses(self):
        return self._ip_addresses

    def outer_xml(self):
        subelements = [
            elem('property', [
                text_elem('name', name),
                text_elem('value', value),
            ])  
            for name, value in self.properties
        ]
        if self.ip_addresses is not None:
            subelements.extend([text_elem('ip_address', ip_address) for ip_address in _list_ips(self.ip_addresses)])

        return elem('vrt_hst', subelements)


class DbServerInfo(Entity):
    def __init__(self, server_type, server_id):
        self._server_type = server_type
        self._server_id = server_id

    @property
    def server_type(self):
        return self._server_type

    @property
    def server_id(self):
        return self._server_id


class SubscriptionAddInfo(Entity):
    def __init__(self, gen_setup, hosting, plan_id=None, plan_name=None):
        self._gen_setup = gen_setup
        self._hosting = hosting
        self._plan_id = plan_id
        self._plan_name = plan_name

    @property
    def gen_setup(self):
        return self._gen_setup

    @property
    def hosting(self):
        return self._hosting

    @property
    def plan_id(self):
        return self._plan_id

    @property
    def plan_name(self):
        return self._plan_name


class SubscriptionAddGenSetup(Entity):
    def __init__(self, name, owner_id, owner_login, htype, ip_address):
        self._name = name
        self._owner_id = owner_id
        self._owner_login = owner_login
        # one of SubscriptionAddHostingType
        self._htype = htype
        self._ip_address = ip_address

    @property
    def name(self):
        return self._name

    @property
    def owner_id(self):
        return self._owner_id

    @property
    def owner_login(self):
        return self._owner_login

    @property
    def htype(self):
        return self._htype

    @property
    def ip_address(self):
        return self._ip_address


class SubscriptionAddHostingType(object):
    VIRTUAL = 'vrt_hst'
    STANDARD_FORWARDING = 'std_fwd'
    FRAME_FORWARDING = 'frm_fwd'


class SubscriptionAddResult(Entity):
    def __init__(self, subscription_id, guid):
        self._subscription_id = subscription_id
        self._guid = guid

    @property
    def subscription_id(self):
        return self._subscription_id

    @property
    def guid(self):
        return self._guid


class SubscriptionOperator(object):
    ERR_SUBSCRIPTION_DOES_NOT_EXIST = 1013

    class Dataset(object):
        GEN_INFO = 'gen_info'
        HOSTING = 'hosting'
        HOSTING_BASIC = 'hosting-basic'
        MAIL = 'mail'
        LIMITS = 'limits'
        SUBSCRIPTIONS = 'subscriptions'
        # Not implemented: 'limits', 'stat', 'prefs', 'disk_usage', 'performance',
        # 'subscriptions', 'permissions', 'plan-items'.

        values = [GEN_INFO, HOSTING, HOSTING_BASIC, MAIL, LIMITS, SUBSCRIPTIONS]
    
    FilterAll = core.FilterAll
    FilterByName = core.declare_filter('FilterByName', 'name')
    FilterById = core.declare_filter('FilterById', 'id')
    FilterByOwnerId = core.declare_filter('FilterByOwnerId', 'owner-id')
    FilterByOwnerLogin = core.declare_filter('FilterByOwnerId', 'owner-login')
    FilterByGuid = core.declare_filter('FilterByGuid', 'guid')
    FilterByOwnerGuid = core.declare_filter('FilterByOwnerGuid', 'owner-guid')
    FilterByExternalId = core.declare_filter('FilterByExternalId', 'external-id')
    FilterByOwnerExternalId = core.declare_filter('FilterByOwnerExternalId', 'owner-external-id')
        
    class Get(core.operation_with_filter('Get', ('dataset',))):
        operator_name = 'webspace'
        operation_name = 'get'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return [
                elem('dataset', [elem(value) for value in SubscriptionOperator.Dataset.values if value in self.dataset])
            ]
            
        @classmethod
        def parse(cls, elem):
            results = [core.Result.parse(r, cls._parse_data) for r in elem.findall('result')]
            if len(results) == 1 and results[0].ok and results[0].data is None:
                results = []
            return results
        
        @classmethod
        def _parse_data(cls, elem):
            subscription_id = if_not_none(elem.findtext('id'), int)
            if subscription_id is None:
                return None
            
            data = elem.find('data')
            gen_info = if_not_none(data.find('gen_info'), cls._parse_gen_info)
            hosting = if_not_none(data.find('hosting'), cls._parse_hosting)
            mail = if_not_none(data.find('mail'), cls._parse_mail)
            limits = if_not_none(data.find('limits'), cls._parse_limits)
            subscriptions = if_not_none(data.find('subscriptions'), cls._parse_subscriptions)
            
            return subscription_id, SubscriptionInfo(gen_info, hosting, mail, limits, subscriptions)
            
        @classmethod
        def _parse_gen_info(cls, elem):
            return SubscriptionGenInfo(
                creation_date=datetime.strptime(elem.findtext('cr_date'), '%Y-%m-%d'),
                name=elem.findtext('name'), ascii_name=elem.findtext('ascii-name'),
                status=int(elem.findtext('status')), real_size=int(elem.findtext('real_size')),
                owner_id=if_not_none(elem.findtext('owner-id'), int),
                owner_login=elem.findtext('owner-login'),
                dns_ip_address=elem.findtext('dns_ip_address'),
                htype=elem.findtext('htype'), guid=elem.findtext('guid'),
                vendor_guid=elem.findtext('vendor-guid'),
                external_id=elem.findtext('external-id'),
                sb_site_uuid=elem.findtext('sb-site-uuid'),
            )

        @classmethod
        def _parse_hosting(cls, elem):
            if elem.find('none') is not None:
                return SubscriptionHostingNone()
            elif elem.find('std_fwd') is not None:
                return SubscriptionHostingStandardForwarding(
                    dest_url=elem.findtext('std_fwd/dest_url'),
                    ip_addresses=_parse_plesk_ips([ip_node.text for ip_node in elem.findall('std_fwd/ip_address')])
                )
            elif elem.find('frm_fwd') is not None:
                return SubscriptionHostingFrameForwarding(
                    dest_url=elem.findtext('frm_fwd/dest_url'),
                    ip_addresses=_parse_plesk_ips([ip_node.text for ip_node in elem.findall('frm_fwd/ip_address')])
                )
            elif elem.find('vrt_hst') is not None:
                properties = dict()
                for property_node in elem.findall('vrt_hst/property'):
                    properties[property_node.findtext('name')] = property_node.findtext('value')

                ip_addresses = _parse_plesk_ips([ip_node.text for ip_node in elem.findall('vrt_hst/ip_address')])

                return SubscriptionHostingVirtual(properties=properties, ip_addresses=ip_addresses)

        @classmethod
        def _parse_mail(cls, elem):
            ip_addresses = _parse_plesk_ips([ip_node.text for ip_node in elem.findall('ip_address')])
            return MailPreferences(ip_addresses)

        @classmethod
        def _parse_limits(cls, elem):
            return LimitsInfo(
                overuse=elem.findtext('overuse'),
                limits={
                    limit_node.findtext('name'): limit_node.findtext('value')
                    for limit_node in elem.findall('limit')
                }
            )

        @classmethod
        def _parse_subscriptions(cls, elem):
            return SubscriptionSubscriptions([
                SubscriptionSubscription(
                    plan=SubscriptionSubscriptionPlan(
                        guid=subscription_node.findtext('plan/plan-guid')
                    )
                )
                for subscription_node in elem.findall('subscription')
            ])

    class Set(core.operation_with_filter('Set', ('hosting', 'limits'))):
        operator_name = 'webspace'
        operation_name = 'set'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return [
                elem(
                    'values', seq(
                        if_not_none(self.hosting, lambda h: elem('hosting', [h.outer_xml()])),
                        if_not_none(self.limits, lambda l: l.outer_xml())
                    )
                ),
            ]

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

    class Add(core.operation('Add', ('subscription_add_info',))):
        operator_name = 'webspace'
        operation_name = 'add'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def inner_xml(self):
            return seq(
                elem('gen_setup', seq(
                    text_elem('name', self.subscription_add_info.gen_setup.name),
                    text_elem('owner-id', self.subscription_add_info.gen_setup.owner_id),
                    text_elem('owner-login', self.subscription_add_info.gen_setup.owner_login),
                    text_elem('htype', self.subscription_add_info.gen_setup.htype),
                    text_elem('ip_address', self.subscription_add_info.gen_setup.ip_address),
                )),
                elem('hosting', [
                    self.subscription_add_info.hosting.outer_xml()
                ]),
                text_elem('plan-id', self.subscription_add_info.plan_id),
                text_elem('plan-name', self.subscription_add_info.plan_name),
            )

        @classmethod
        def parse(cls, elem):
            return core.Result.parse(elem.find('result'), cls._parse_data)
        
        @classmethod
        def _parse_data(cls, elem):
            return SubscriptionAddResult(
                subscription_id=elem.findtext('id'),
                guid=elem.findtext('guid')
            )

    class AddSubscription(core.operation_with_filter('AddSubscription', ('plan_guid',))):
        operator_name = 'webspace'
        operation_name = 'add-subscription'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return seq(
                text_elem('plan-guid', self.plan_guid)
            )

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

    class SyncSubscription(core.operation_with_filter('SyncSubscription')):
        operator_name = 'webspace'
        operation_name = 'sync-subscription'
        min_api_version = '1.6.3.0'
        max_api_version = None

        def data_xml(self):
            return []

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

    class DbServers(object):

        class List(core.operation_with_filter('List', ())):
            operator_name = 'webspace'
            operation_name = 'db-servers'

            min_api_version = '1.6.4.0'
            max_api_version = None

            def inner_xml(self):
                return [
                    elem('list', [elem('filter', self.filter.inner_xml())])
                ]

            @classmethod
            def parse(cls, elem):
                return [core.Result.parse(r, cls._parse_data) for r in elem.findall('list/result')]
        
            @classmethod
            def _parse_data(cls, elem):
                return (
                    int(elem.findtext('id')),  # subscription_id
                    [
                        DbServerInfo(
                            server_type=db_server_node.findtext('type'),
                            server_id=int(db_server_node.findtext('id'))
                        )
                        for db_server_node in elem.findall('db-server')
                    ]
                )
