import logging
import itertools
import re
from collections import namedtuple

from parallels.utils.ip import is_ipv4, is_ipv6

Rec = namedtuple('Rec', ('rec_type', 'src', 'dst', 'opt'))

logger = logging.getLogger(__name__)

def replace_resource_record_ips(subscription):
	"""Replace source panel IPs with target IPs in subscription DNS zone data.
	
	Assuming, that each source subscription has a single assigned IP address of
	each type (IPv4/IPv6) used by all its services, replace that IP with the
	new ones of the target server.
	"""
	target_ips = []
	if subscription.target_public_web_ipv4 is not None:
		target_ips.append(subscription.target_public_web_ipv4)
	if subscription.target_public_web_ipv6 is not None:
		target_ips.append(subscription.target_public_web_ipv6)

	if len(target_ips) == 0:
		# Subscription has no web hosting, try to take IP of mail hosting.
		# In most cases that should work fine.
		if subscription.target_public_mail_ipv4 is not None:
			target_ips.append(subscription.target_public_mail_ipv4)

	if len(target_ips) == 0:
		logger.error(
			u"Subscription '%s' has no IP addresses on target system. "
			u"DNS records won't be changed" % (subscription.name)
		)
		return

	source_ips = [subscription.converted_backup.ip, subscription.converted_backup.ipv6]

	for domain in itertools.chain(
		subscription.converted_backup.iter_domains(),
		subscription.converted_backup.iter_aliases()
	):
		if domain.dns_zone is None:
			continue

		dns_zone = UniqueDnsZoneAdapter(domain.dns_zone)
		for rec in list(domain.dns_zone.iter_dns_records()):
			if rec.dst in source_ips:
				dns_zone.remove_dns_record(rec)
				for new_ip in target_ips:
					rec_type = rec.rec_type
					if rec_type in ('A', 'AAAA'):
						if is_ipv4(new_ip):
							rec_type = 'A'
						elif is_ipv6(new_ip):
							rec_type = 'AAAA'

					dns_zone.add_dns_record(Rec(
						rec_type=rec_type, src=rec.src, 
						dst=new_ip, opt=rec.opt
					))
					logger.debug(u"Replaced IP in %s with %s" % (
						rec, new_ip
					))

class UniqueDnsZoneAdapter(object):
	"""DNS zone adapter which makes changed DNS records unique.

	This class makes resulting DNS records unique: skip duplicates. Example:
	when there are 2 source web IPs, but only one target web IP. 
	"""
	def __init__(self, dns_zone):
		self.dns_zone = dns_zone

		self.xml_recs = {
			self._create_rec(r): r
			for r in dns_zone.iter_dns_records()
		}

	def remove_dns_record(self, rec):
		rec = self._create_rec(rec)
		self.dns_zone.remove_dns_record(self.xml_recs[rec])
		del self.xml_recs[rec]

	def add_dns_record(self, rec):
		rec = self._create_rec(rec)
		if rec not in self.xml_recs:
			self.xml_recs[rec] = self.dns_zone.add_dns_record(rec)
		else:
			pass

	@staticmethod
	def _create_rec(xml_rec):
		return Rec(
			rec_type=xml_rec.rec_type, src=xml_rec.src, 
			dst=xml_rec.dst, opt=xml_rec.opt
		)

def render_record_value(value, vars):
	"""Expand DNS template record value using given parameter values.

	DNS record value looks like "mail.<domain>." or "<ipv6.mail>".
	Plesk omits records for which referenced parameter wasn't found.

	vars: parameters dictionary.

	Returns: string or None if any referenced value isn't found
	  in parameters or it's value is None.
	"""
	class NoVar(BaseException): pass
	try:
		def expand(match):
			v = vars.get(match.group(1))
			if v is None:
				raise NoVar()
			return v
		return re.sub(r'<([\w.]+)>', expand, value)
	except NoVar:
		return None

def render_dns_record_template(record, values):
	src = render_record_value(record.src, values)
	dst = render_record_value(record.dst, values)
	if src is None or dst is None:
		return None
	else:
		return Rec(record.rec_type, src, dst, record.opt)

def render_dns_zone_template(dns_zone_template, variables):
	"""Instantiate DNS zone template with given parameters.

	dns_zone_template: sequence of Rec
	variables: dictionary

	Returns dictionary where keys are instantiated DNS records and
	values are corresponding template records.

	If DNS template record can't be expanded (for example, because
	it refers unknown parameter) it is silently skipped.
	"""
	zone = {}
	for tmpl_rec in dns_zone_template:
		rec = render_dns_record_template(tmpl_rec, variables)
		if rec is not None:
			zone[rec] = tmpl_rec

	return zone

def extract_custom_records(dns_zone_template, variables, records):
	"""Partition records into standard and custom ones.

	Returns pair:
	# first element is dictionary with records as keys and record templates as values
	# second is list of custom DNS records
	"""
	# Instantiate DNS zone template with given parameters.
	standard_records = render_dns_zone_template(dns_zone_template, variables)
	
	template_recs = {}
	custom_recs = []

	# Now enumerate all existing records and check whether it
	# could be generated from template or not.
	for rec in records:
		if rec in standard_records:
			template_recs[rec] = standard_records[rec]
		else:
			custom_recs.append(rec)

	return template_recs, custom_recs

def pretty_record_str(rec):
	opt_str = u" %s" % (rec.opt,) if rec.opt is not None and rec.opt != "" else ""
	return u"%s %s%s %s" % (rec.src, rec.rec_type, opt_str, rec.dst)

