import itertools

from collections import namedtuple

from parallels.api.plesk.operator.dns import DnsOperator, Soa
from parallels.api.plesk.operator.dns9 import DnsOperator9
from parallels.api.plesk.operator.subdomain import SubdomainOperator
from parallels.api.plesk.operator.alias import AliasOperator
from parallels.api.plesk.operator.domain_alias import DomainAliasOperator
from parallels.api.plesk.operator.site import SiteOperator
from parallels.api.plesk.operator.subscription import SubscriptionOperator
from parallels.api.plesk.operator.domain import DomainOperator
from parallels.core import version_tuple
from parallels.core.utils.common import split_list_by_count
from parallels.core.utils.yaml_utils import pretty_yaml

DnsTimings = pretty_yaml(namedtuple('DnsTimings', ('sites', 'aliases')))
SOA = pretty_yaml(namedtuple('SOA', ('ttl', 'refresh', 'retry', 'expire', 'minimum')))

# On large source installations (with more than 500 domains) Plesk failes to perform some queries 
# if filter is too large because of Plesk PHP memory limits. 
# So we split every large query into several small ones so filter length is less or equal to MAX_PLESK_API_FILTER_LENGTH.
MAX_PLESK_API_FILTER_LENGTH = 128

def set_low_dns_timings(plesk_api, backup, ttl_value):
	low_timing_soa = Soa(
		ttl = ttl_value,
		refresh = ttl_value,
		retry = ttl_value,
		expire = None, # Do not change.
		minimum = ttl_value,
	)
		
	plesk_api_helper = _get_plesk_api_helper(plesk_api)

	if version_tuple(plesk_api.api_version) <= (1,6,0,2):
		_execute_plesk_api_splitted_no_result(
			lambda domain_ids: plesk_api_helper.set_soa_for_domains(domain_ids, low_timing_soa),
			[id for id, _ in _get_all_domains(plesk_api, backup)]
		)
	else:
		_execute_plesk_api_splitted_no_result(
			lambda site_ids: plesk_api_helper.set_soa_for_sites(site_ids, low_timing_soa),
			[id for id, _ in _get_all_sites(backup, plesk_api)]
		)

	_execute_plesk_api_splitted_no_result(
		lambda alias_ids: plesk_api_helper.set_soa_for_aliases(alias_ids, low_timing_soa),
		[id for id, _ in _get_all_aliases(plesk_api, backup)]
	)

def get_dns_timings(plesk_api, backup):
	if version_tuple(plesk_api.api_version) <= (1,6,0,2):
		return DnsTimings(
			sites = _get_domains_dns_timings(plesk_api, backup),
			aliases = _get_aliases_dns_timings(plesk_api, backup)
		)
	else:
		return DnsTimings(
			sites = _get_sites_dns_timings(plesk_api, backup),
			aliases = _get_aliases_dns_timings(plesk_api, backup)
		)

def _get_sites_dns_timings(plesk_api, backup):
	all_sites = _get_all_sites(backup, plesk_api)
	site_soa = _execute_plesk_api_splitted(Plesk10ApiHelper(plesk_api).get_soa_for_sites, [id for id, _ in all_sites])
	site_names_by_id = dict(all_sites)
	return {site_names_by_id[site_id]: _pretty_yaml_soa(soa) for site_id, soa in site_soa}

def _get_domains_dns_timings(plesk_api, backup):
	all_domains = _get_all_domains(plesk_api, backup)
	domain_soa = _execute_plesk_api_splitted(Plesk9ApiHelper(plesk_api).get_soa_for_domains, [id for id, _ in all_domains])
	domain_names_by_id = dict(all_domains)
	return {domain_names_by_id[domain_id]: _pretty_yaml_soa(soa) for domain_id, soa in domain_soa}

def _get_aliases_dns_timings(plesk_api, backup):
	all_aliases = _get_all_aliases(plesk_api, backup)
	alias_names_by_id = dict(all_aliases)
	plesk_api_helper = _get_plesk_api_helper(plesk_api)
	alias_soa = _execute_plesk_api_splitted(plesk_api_helper.get_soa_for_aliases, [id for id, _ in all_aliases])
	return {alias_names_by_id[alias_id]: _pretty_yaml_soa(soa) for alias_id, soa in alias_soa}

def _get_all_domains(plesk_api, backup):
	"""Get all domains from Plesk 9
	Returns id-name pairs for each domain
	"""
	return _execute_plesk_api_splitted(
		Plesk9ApiHelper(plesk_api).get_domain_id_name_pair,
		BackupHelper(backup).get_all_subscription_names(), 
	)

def _get_all_sites(backup, plesk_api):
	backup_helper = BackupHelper(backup)
	plesk_api_helper = Plesk10ApiHelper(plesk_api)

	return _execute_plesk_api_splitted(plesk_api_helper.get_subscription_id_name_pair, backup_helper.get_all_subscription_names()) + \
		_execute_plesk_api_splitted(plesk_api_helper.get_addon_domain_id_name_pair, backup_helper.get_all_addon_names()) + \
		_execute_plesk_api_splitted(plesk_api_helper.get_subdomain_id_name_pair, backup_helper.get_all_subdomain_names())

def _get_all_aliases(plesk_api, backup):
	"""Returns id-name pairs for each domain alias
	"""
	return _execute_plesk_api_splitted(
		_get_plesk_api_helper(plesk_api).get_alias_id_name_pair, 
		BackupHelper(backup).get_all_alias_names()
	)

def _pretty_yaml_soa(soa):
	return SOA(ttl=soa.ttl, refresh=soa.refresh, retry=soa.retry, expire=soa.expire, minimum=soa.minimum)

def _execute_plesk_api_splitted(method, filter_items):
	result = []
	for filter_items_batch in split_list_by_count(filter_items, MAX_PLESK_API_FILTER_LENGTH):
		result.extend(method(filter_items_batch))
	return result

def _execute_plesk_api_splitted_no_result(method, filter_items):
	for filter_items_batch in split_list_by_count(filter_items, MAX_PLESK_API_FILTER_LENGTH):
		method(filter_items_batch)

def _send_and_check(plesk_api, operator):
	responses = plesk_api.send(operator)
	for response in responses:
		response.check()

def _get_plesk_api_helper(plesk_api):
	if version_tuple(plesk_api.api_version) <= (1,6,0,2):
		return Plesk9ApiHelper(plesk_api)
	else:
		return Plesk10ApiHelper(plesk_api)

class Plesk10ApiHelper:
	def __init__(self, plesk_api):
		self.plesk_api = plesk_api

	def get_subscription_id_name_pair(self, subscription_names):
		return [
			(subscription.data[0], subscription.data[1].gen_info.name)
			for subscription in self.plesk_api.send(SubscriptionOperator.Get(
				filter = SubscriptionOperator.FilterByName(subscription_names),
				dataset = [SubscriptionOperator.Dataset.GEN_INFO]
			))
		]

	def get_addon_domain_id_name_pair(self, addon_domain_names):
		return [
			(site.data[0], site.data[1].gen_info.name)
			for site in self.plesk_api.send(SiteOperator.Get(
				filter = SiteOperator.FilterByName(addon_domain_names),
				dataset = [SiteOperator.Dataset.GEN_INFO]
			))
		]

	def get_subdomain_id_name_pair(self, subdomain_names):
		return [
			(int(subdomain.data[0]), subdomain.data[1].name)
			for subdomain in self.plesk_api.send(SubdomainOperator.Get(
				filter=SubdomainOperator.FilterByName(subdomain_names),
			))
		]

	def get_alias_id_name_pair(self, alias_names):
		return [
			(alias.data[0], alias.data[1].name)
			for alias in self.plesk_api.send(AliasOperator.Get(
				filter=AliasOperator.FilterByName(alias_names),
			))
		]

	def get_soa_for_sites(self, site_ids):
		return [
			(result.data.object_id, result.data.soa)
			for result in self.plesk_api.send(DnsOperator.Get(
				filter=DnsOperator.FilterBySiteId(site_ids),
				soa=True
			))
		]

	def get_soa_for_aliases(self, alias_ids):
		return [
			(result.data.object_id, result.data.soa)
			for result in self.plesk_api.send(DnsOperator.Get(
				filter=DnsOperator.FilterBySiteAliasId(alias_ids),
				soa=True
			))
		]

	def set_soa_for_sites(self, site_ids, soa):
		_send_and_check(self.plesk_api, DnsOperator.Set(
			filter=DnsOperator.FilterBySiteId(site_ids),
			soa=soa 
		))

	def set_soa_for_aliases(self, alias_ids, soa):
		_send_and_check(self.plesk_api, DnsOperator.Set(
			filter = DnsOperator.FilterBySiteAliasId(alias_ids),
			soa=soa
		))

class Plesk9ApiHelper:
	def __init__(self, plesk_api):
		self.plesk_api = plesk_api

	def get_domain_id_name_pair(self, domain_names):
		if version_tuple(self.plesk_api.api_version) < (1,6,0,0):
			filter = DomainOperator.FilterByName8(domain_names)
		else:
			filter = DomainOperator.FilterByDomainName(domain_names)
		return [
			(site.data[0], site.data[1].gen_info.name)
			for site in self.plesk_api.send(DomainOperator.Get(
				filter=filter,
				dataset=[DomainOperator.Dataset.GEN_INFO]
			))
		]
	
	def get_alias_id_name_pair(self, alias_names):
		return [
			(alias.data[0], alias.data[1].name)
			for alias in self.plesk_api.send(DomainAliasOperator.Get(
				filter=DomainAliasOperator.FilterByName(alias_names),
			))
		]

	def get_soa_for_domains(self, domain_ids):
		return [
			(result.data.object_id, result.data.soa)
			for result in self.plesk_api.send(DnsOperator9.Get(
				filter=DnsOperator9.FilterByDomainId(domain_ids),
				soa=True
			))
		]

	def get_soa_for_aliases(self, alias_ids):
		return [
			(result.data.object_id, result.data.soa)
			for result in self.plesk_api.send(DnsOperator9.Get(
				filter=DnsOperator9.FilterByDomainAliasId(alias_ids),
				soa=True
			))
		]

	def set_soa_for_domains(self, domain_ids, soa):
		_send_and_check(self.plesk_api, DnsOperator9.Set(
			filter=DnsOperator9.FilterByDomainId(domain_ids),
			soa=soa 
		))

	def set_soa_for_aliases(self, alias_ids, soa):
		_send_and_check(self.plesk_api, DnsOperator9.Set(
			filter=DnsOperator9.FilterByDomainAliasId(alias_ids),
			soa=soa
		))

class BackupHelper:
	def __init__(self, backup):
		self.backup = backup

	def get_all_subscription_names(self):
		return [subscription.name for subscription in self.backup.iter_all_subscriptions()]

	def get_all_addon_names(self):
		return [
			addon_domain.name
			for subscription_name in self.get_all_subscription_names()
			for addon_domain in self.backup.iter_addon_domains(subscription_name)
		]

	def get_all_subdomain_names(self):
		return [
			subdomain.name
			for subscription_name in self.get_all_subscription_names()
			for domain_name in itertools.chain(
				[subscription_name], 
				[addon_domain.name for addon_domain in self.backup.iter_addon_domains(subscription_name)]
			)
			for subdomain in self.backup.iter_subdomains(subscription_name, domain_name)
		]

	def get_all_alias_names(self):
		return [
			alias.name
			for subscription_name in self.get_all_subscription_names()
			 for alias in self.backup.iter_aliases(subscription_name)
		]

