from parallels.combination.ppa_sn_to_ppa_sn import messages
from collections import namedtuple, defaultdict

from parallels.api.plesk.operator.subscription import Ips
from parallels.api.plesk import operator as plesk_ops
from parallels.api.plesk.core import PleskError, PleskErrorCode
from parallels.core.utils.common import format_list, find_only
from parallels.core.utils.common import cached
from parallels.core.utils import migrator_utils
from .common import WebServiceType, HostingType

SubscriptionInfo = namedtuple('SubscriptionInfo', ('name', 'web_ips', 'mail_ips', 'hosting_type'))
SiteInfo = namedtuple('SiteInfo', ('name', 'hosting_type',))

class PPAData(object):
	def __init__(self, conn):
		self.conn = conn

	@cached
	def get_subscription_info(self, subscription_name):
		"""Get information about subscription in PPA

		Arguments:
		- subscription_name - name of subscription (webspace) as a string

		Returns SubscriptionInfo object if subscription exists in PPA,
		otherwise raises SubscriptionNotFoundException.

		The function is cached, so use carefull if some subscription's data
		changes during move.
		"""

		subscriptions = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Get(
				plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
				[
					plesk_ops.SubscriptionOperator.Dataset.GEN_INFO,
					plesk_ops.SubscriptionOperator.Dataset.HOSTING_BASIC,
					plesk_ops.SubscriptionOperator.Dataset.MAIL,
				]
			)
		)

		no_subscription_exception = SubscriptionNotFoundException(
			messages.SUBSCRIPTION_WITH_NAME_WAS_NOT_FOUND % (
				subscription_name
			)
		)

		if len(subscriptions) == 0:
			raise no_subscription_exception

		try:
			subscription = subscriptions[0].data[1]
		except PleskError as e:
			if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST:
				raise no_subscription_exception

		name = subscription.gen_info.name
		hosting = subscription.hosting
		mail = subscription.mail

		if isinstance(hosting, plesk_ops.SubscriptionHostingVirtual):
			hosting_type = HostingType.Virtual
		elif isinstance(hosting, plesk_ops.SubscriptionHostingStandardForwarding):
			hosting_type = HostingType.StandardForwarding
		elif isinstance(hosting, plesk_ops.SubscriptionHostingFrameForwarding):
			hosting_type = HostingType.FrameForwarding
		else:
			hosting_type = HostingType.NoHosting
		
		return SubscriptionInfo(
			name=name,
			web_ips=(
				hosting.ip_addresses if hosting_type in (HostingType.Virtual, HostingType.StandardForwarding, HostingType.FrameForwarding)
				else Ips(None, None)
			),
			mail_ips=mail.ip_addresses,
			hosting_type=hosting_type,
		)

	def get_subscription_sysuser_name(self, subscription_name):
		"""
		Return system user login for subscription by subscription name. If subscriptions does not have virtual hosting - return None.
		"""
		subscriptions = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Get(
				plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
				[
					plesk_ops.SubscriptionOperator.Dataset.GEN_INFO,
					plesk_ops.SubscriptionOperator.Dataset.HOSTING_BASIC,
				]
			)
		)
		if len(subscriptions) != 1:
			raise Exception(messages.FAILED_TO_GET_SYSTEM_USER % (subscription_name,))
		subscription = subscriptions[0].data[1]
		if subscription.gen_info.htype == HostingType.Virtual:
			return subscription.hosting.properties['ftp_login']
		else:
			return None

	def get_subscription_sites_info(self, subscription_name):
		return [
			SiteInfo(name=result.data[1].gen_info.name, hosting_type=self._get_hosting_type_from_str(result.data[1].gen_info.htype)) 
			for result in self.conn.plesk_api().send(
				plesk_ops.SiteOperator.Get(
					filter=plesk_ops.SiteOperator.FilterByParentName([subscription_name]),
					dataset=[plesk_ops.SiteOperator.Dataset.GEN_INFO]
				)
			)
		]

	def get_subdomain_names(self, domain_names):
		return [
			result.data[1].name for result in self.conn.plesk_api().send(
				plesk_ops.SubdomainOperator.Get(
					filter=plesk_ops.SubdomainOperator.FilterByParentName(domain_names)
				)
			)
		]

	def get_domain_aliases(self, domain_name):
		return [result.data[1] for result in self.conn.plesk_api().send(
			plesk_ops.AliasOperator.Get(
				plesk_ops.AliasOperator.FilterBySiteName([domain_name]),
			)
		)]

	def get_domain_alias_dns_records(self, alias_id):
		return [result.data for result in self.conn.plesk_api().send(
			plesk_ops.DnsOperator.GetRec(filter=plesk_ops.DnsOperator.FilterBySiteAliasId([alias_id]), template=None)
		)]

	def delete_dns_record(self, record_id):
		result = self.conn.plesk_api().send(
			plesk_ops.DnsOperator.DelRec(filter=plesk_ops.DnsOperator.DelRec.FilterById([record_id]), template=None)
		)
		if len(result) != 1:
			raise Exception(messages.FAILED_TO_REMOVE_DNS_RECORD_EXPECTED_EXACTLY_ONE % (record_id,))
		if result[0].data.id != record_id:
			raise Exception(messages.FAILED_TO_REMOVE_DNS_RECORD_RESPONSE_ID_NOT_EQUAL % (record_id, result[0].id))

	def add_domain_alias_dns_record(self, alias_id, src, dst, opt, rec_type): 
		result = self.conn.plesk_api().send(
			plesk_ops.DnsOperator.AddRec(
				target=plesk_ops.DnsOperator.AddRec.TargetSiteAliasId(id=alias_id),
				src=src, dst=dst, opt=opt, rec_type=rec_type,
			)
		)
		if len(result) != 1:
			raise Exception(messages.FAILED_TO_ADD_DNS_RECORD_INVALID_RESPONSE)
		result[0].check()

	def get_webspace_id_by_name(self, webspace_name):
		result = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Get(
				dataset=[plesk_ops.SubscriptionOperator.Dataset.GEN_INFO],
				filter=plesk_ops.SubscriptionOperator.FilterByName([webspace_name]),
			)
		)
		if len(result) != 1:
			raise Exception(messages.FAILED_TO_GET_WEBSPACE_ID_BY_NAME % (webspace_name))

		return result[0].data[0]

	def get_db_server_id_by_host(self):
		return dict((result.data.host, result.data.id) for result in self.conn.plesk_api().send(
			plesk_ops.DbServerOperator.Get(
				filter=plesk_ops.DbServerOperator.FilterAll()
			)
		))

	def get_db_host_by_server_id(self):
		return dict((result.data.id, result.data.host) for result in self.conn.plesk_api().send(
			plesk_ops.DbServerOperator.Get(
				filter=plesk_ops.DbServerOperator.FilterAll()
			)
		))

	def get_mysql_db_server_hosts(self):
		return set([
			result.data.host for result in self.conn.plesk_api().send(
				plesk_ops.DbServerOperator.Get(
					filter=plesk_ops.DbServerOperator.FilterAll()
				)
			) if result.data.dbtype == 'mysql'
		])

	def get_db_servers_with_passwords(self):
		with self.conn.main_node_runner() as runner:
			with migrator_utils.plain_passwords_enabled(runner): # temporary enable ability to fetch passwords via API
				return [result.data for result in self.conn.plesk_api().send(
					plesk_ops.DbServerOperator.Get(
						filter=plesk_ops.DbServerOperator.FilterAll()
					)
				)]

	def get_mysql_databases(self, subscription_name):
		return [result.data for result in self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.GetDb(
				filter=plesk_ops.DatabaseOperator.GetDb.FilterByWebspaceName([subscription_name]),
			)
		)]

	def create_mysql_database(self, webspace_id, db_server_id, db_name):
		result = self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.AddDb(
				webspace_id=webspace_id, name=db_name, type='mysql', db_server_id=db_server_id,
			)
		)
		result.check()

	def get_default_mysql_server_id(self, subscription_name):
		subscriptions = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Get(
				filter=plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
				dataset=[plesk_ops.SubscriptionOperator.Dataset.GEN_INFO]
			)
		)
		
		if len(subscriptions) != 1:
			raise Exception(messages.FAILED_TO_GET_DEFAULT_MYSQL_SERVER_EXPECTED_EXACTLY_ONE_SUBSCRIPTION % (subscription_name, len(subscriptions)))

		db_servers = {
			result.data[0]: result.data[1] for result in self.conn.plesk_api().send(
				plesk_ops.SubscriptionOperator.DbServers.List(
					plesk_ops.SubscriptionOperator.FilterAll()
				)
			)
		}
		subscription_id = subscriptions[0].data[0]
		return find_only(
			db_servers[subscription_id], 
			lambda db_server: db_server.server_type == 'mysql', 
			messages.FAILED_TO_GET_DEFAULT_MYSQL_SERVER_EXPECTED_EXACTLY_ONE_SERVER).server_id

	def get_db_admin_user_id(self, db_id):
		result = self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.GetDefaultUser(
				plesk_ops.DatabaseOperator.GetDefaultUser.FilterByDbIds([db_id])
			)
		)
		if len(result) != 1:
			raise Exception(messages.FAILED_TO_GET_DATABASE_ADMINISTRATOR_ID_MULTIPLE_RESULTS % (db_id))
		try:
			response_db_id = result[0].data.db_id 
			if response_db_id != db_id:
				raise Exception(messages.FAILED_TO_GET_DATABASE_ADMIN_RESPONSE_ID_NOT_EQUAL % (db_id, response_db_id, db_id))
			return result[0].data.db_user_id
		except PleskError as e: # XXX in case there is no default, Plesk API returns error, and there is no reliable way to distinguish it from other errors, so we use error message to do this 
			if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST and e.message == 'default user does not exist':
				return None
			else:
				raise

	def set_db_admin_user_id(self, db_id, db_user_id):
		result = self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.SetDefaultUser(db_id, db_user_id)
		)
		result.check()

	def get_db_users_for_databases(self, db_ids):
		results = self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.GetDbUsers(
				plesk_ops.DatabaseOperator.GetDbUsers.FilterByDbIds(db_ids)
			)
		)
		db_users = defaultdict(list)
		for result in results:
			db_users[result.data.db_id].append(result.data)
		return db_users

	def delete_databases(self, db_ids):
		if len(db_ids) == 0: 
			# simply return, don't call Plesk API to avoid problem while passing empty filter node "<filter/>" with no IDs 
			# which is interpreted as "remove all databases" instead of "remove no databases" by Plesk API
			return

		results = self.conn.plesk_api().send(
			plesk_ops.DatabaseOperator.DelDb(
				plesk_ops.DatabaseOperator.DelDb.FilterByDbIds(db_ids),
			)
		)
		deleted_db_ids = [result.data.id for result in results]
		if set(db_ids) != set(deleted_db_ids):
			raise Exception(u"Not all databases were deleted: requested %s, deleted %s", format_list(db_ids), format_list(deleted_db_ids))

	def get_database_limits(self, subscription_name):
		results = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Get(
				filter=plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
				dataset=[plesk_ops.SubscriptionOperator.Dataset.LIMITS]
			)
		)
		if len(results) != 1:
			raise Exception(messages.FAILED_TO_GET_DATABASE_LIMIT % (subscription_name,))
		
		return {key: int(results[0].data[1].limits.limits[key]) for key in ('max_db', 'mysql_dbase_space')}

	def set_subscription_limits(self, subscription_name, limits):
		if len(limits) == 0:
			return

		results = self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Set(
				filter=plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
				hosting=None,
				limits=plesk_ops.LimitsInfo(overuse=None, limits=limits)
			)
		)
		if len(results) != 1:
			raise Exception(messages.FAILED_TO_SET_DATABASE_LIMIT_MULTIPLE_RESULTS % (subscription_name,))

		results[0].check()

	def get_web_type(self, domain_name, apache_resource_type_ids, iis_resource_type_ids):
		def get_resource_type_ids_by_webspace(domain_name):
			poa_api = self.conn.poa_api()
			return set(poa_api.get_webspace(
				poa_api.get_webspace_id_by_primary_domain(domain_name)
			).get('rt_ids', []))

		webspace_resource_type_ids = get_resource_type_ids_by_webspace(domain_name) 

		if len(webspace_resource_type_ids & apache_resource_type_ids) > 0:
			return WebServiceType.Apache
		elif len(webspace_resource_type_ids & iis_resource_type_ids) > 0:
			return WebServiceType.IIS
		else:
			return None

	def get_apache_resource_type_ids(self):
		return self._get_rt_ids_by_class_name('pleskweb_hosting')

	def get_iis_resource_type_ids(self):
		return self._get_rt_ids_by_class_name('pleskwebiis_hosting')

	def _get_rt_ids_by_class_name(self, class_name):
		poa_api = self.conn.poa_api()
		return set([i.get('resource_type_id') for i in poa_api.get_resource_types_by_class(class_name)])

	@staticmethod
	def _get_hosting_type_from_str(hosting_type_str):
		if hosting_type_str == 'vrt_hst':
			return HostingType.Virtual
		elif hosting_type_str == 'std_fwd':
			return HostingType.StandardForwarding
		elif hosting_type_str == 'frm_fwd':
			return HostingType.FrameForwarding
		elif hosting_type_str == 'none':
			return HostingType.NoHosting

		raise Exception(u"Unknown hosting type string: '%s'" % (hosting_type_str,))

class SubscriptionNotFoundException(Exception):
	pass
