from collections import namedtuple
from itertools import chain
from datetime import datetime

from parallels.core.utils.common import if_not_none, obj

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

Ips = namedtuple('Ips', ('v4', '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,
	)

SubscriptionInfo = namedtuple('SubscriptionInfo', (
	'gen_info', 'hosting', 'mail', 'limits', 'subscriptions'
))

SubscriptionGenInfo = namedtuple('SubscriptionGenInfo', (
	'creation_date', 'name', 'ascii_name', 'status', 'real_size', 'owner_id', 'owner_login', 'dns_ip_address',
	'htype', 'guid', 'vendor_guid', 'external_id', 'sb_site_uuid',
))
SubscriptionSubscriptions = namedtuple('SubscriptionSubscriptions', (
	'subscriptions', # list of SubscriptionSubscription
))
SubscriptionSubscription = namedtuple('SubscriptionSubscription', (
	'plan', # instance of SubscriptionSubscriptionPlan
))
SubscriptionSubscriptionPlan = namedtuple('SubscriptionSubscriptionPlan', (
	'guid',
))


class LimitsInfo(namedtuple('LimitsInfo', ('overuse', '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.iteritems()
			] if self.limits is not None else []
		)

MailPreferences = namedtuple('MailPreferences', ('ip_addresses'))


class SubscriptionHostingNone(namedtuple('SubscriptionHostingNone', ())):
	def outer_xml(self):
		return elem('none')


class SubscriptionHostingStandardForwarding(namedtuple('SubscriptionHostingStandardForwarding', ('dest_url', '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(namedtuple('SubscriptionHostingFrameForwarding', ('dest_url', '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(namedtuple('SubscriptionHostingVirtual', ('properties', '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)

DbServerInfo = namedtuple('DbServerInfo', ('server_type', 'server_id'))

SubscriptionAddInfo = namedtuple('SubscriptionAddInfo', ('gen_setup', 'hosting', 'plan_id'))
SubscriptionAddGenSetup = namedtuple('SubscriptionAddGenSetup', (
	'name', 
	'owner_id',
	'htype', # one of SubscriptionAddHostingType
	'ip_address'
))
SubscriptionAddHostingType = obj(VIRTUAL='vrt_hst', STANDARD_FORWARDING='std_fwd', FRAME_FORWARDING='frm_fwd')
SubscriptionAddResult = namedtuple('SubscriptionAddResult', ('subscription_id', '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', [
					text_elem('name', self.subscription_add_info.gen_setup.name),
					text_elem('owner-id', self.subscription_add_info.gen_setup.owner_id),
					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),
			)

		@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')
					]
				)