"""
When migrating resource usage we try to solve the following 2 tasks:

1) Make customer pay the same amount of money for the subscription as before migration.
That implies that customer should pay for same amount of additional resources as before migration.
For example, if customer bought additional 100GB of traffic, regardless of his actual usage,
he paid some money for these 100GB in H-Sphere. And he should continue to pay for these 100GB after migration,
even if he used only 50GB/month of these.

2) If customer used additional resources in H-Sphere, but target POA service template is configured in
a way so limit is the same as H-Sphere free resource limit, provisioning may fail. 
For example, customer has 5 free domains in H-Sphere plan, and he bought additional 2 domains with extra package.
All 7 domains were actually created in H-Sphere. When migrating to POA service template with 5 domain limit,
migration would fail on restore hosting settings stage when trying to create the 6th and the 7th domains.

Briefly, to calculate additional resources the customer has bought, PPAB retrieves POA subscription resource limit 
and compares it with free amount of resources in PPAB plan. POA service template resource limits are completely ignored.

So, to resolve the 2 tasks we calculate POA subscription resource limit in the following way:
1) For domain, subdomain and mailbox limit:
  POA resource limit = MAX(
	H-Sphere free resource limit + SUM(H-Sphere extra package limits), # this makes pre-paid amount of resources, that customer always pays
	actual resource usage in H-Sphere # this makes resources over pre-paid amount
  )
2) For disk space and traffic:
  POA resource limit = MAX(
	H-Sphere free resource limit + SUM(H-Sphere extra package limits), 
	limit set by customer in the control panel
  )

By "actual resource usage in H-Sphere" we take resource usage *after conversion* to PPA, not H-Sphere resource usage. 
So, for example if you have a parked domain, it will be included into 'domain' resource actual usage, as parked domains are migrated to PPA domains.
Otherwise the problem #2 may arise.

In all cases we assume that:
1) H-Sphere free resource limit == PPAB free resource limit
2) H-Sphere additional resource cost ~= PPAB additional resource cost
Otherwise it will be very difficult to keep the same amount of money a customer pays for subscription.

Please consider that these ways to calculate resource usage don't cover all possible cases, as H-Sphere and PPAB billing models
are quite different. But they seem to be the most appropriate for that moment.
"""

import logging
from collections import namedtuple
from parallels.common.utils.yaml_utils import pretty_yaml

logger = logging.getLogger(__name__)

SubscriptionUsages = namedtuple('SubscriptionUsages', (
	'name', # subscription name
	'domain', # domain usage according to what we are going to restore to PPA
	'subdomain', # subdomain usage according to what we are going to restore to PPA
	'mailbox' # mailbox usage according to what we are going to restore to PPA
))

class ResourceType:
	DOMAIN = 'domain'
	SUBDOMAIN = 'subdomain'
	MAILBOX = 'mailbox'
	DISK_SPACE = 'disk-space' # in KB
	TRAFFIC = 'traffic' # in KB

ResourceLimitModel = pretty_yaml(namedtuple('ResourceLimitModel', (
	'subscriptions', # list of SubscriptionResourceLimits
)))
SubscriptionResourceLimits = pretty_yaml(namedtuple('SubscriptionResourceLimits', (
	'name', # subscription name, string
	'limits' # list of ResourceLimit 
)))
ResourceLimit = pretty_yaml(namedtuple('ResourceLimit', (
	'name', # resource name, one of ResourceType
	'limit' # limit value, number
)))

class BackupAgent:
	def __init__(self, connection_cursor, try_subscription):
		self.conn = connection_cursor
		self.try_subscription = try_subscription

	def make_backup(self, subscription_usages):
		hs_resource_types = {
			ResourceType.DOMAIN: 2,
			ResourceType.SUBDOMAIN: 31,
			ResourceType.MAILBOX: 1002,
		}
		model = ResourceLimitModel(subscriptions=[])
		for subscription in subscription_usages:
			with self.try_subscription(subscription.name, u"Failed to retrieve resource limits from H-Sphere."):
				logger.debug("Retrieve subscription limits of '%s'", subscription.name)
				subscription_resource_limits = SubscriptionResourceLimits(name=subscription.name, limits=[])
				model.subscriptions.append(subscription_resource_limits)

				subscription_period = self._get_subscription_period(subscription.name)
				
				for resource_type in [ResourceType.DOMAIN, ResourceType.SUBDOMAIN, ResourceType.MAILBOX]:
					hs_resource_type = hs_resource_types[resource_type]
					subscription_resource_limits.limits.append(ResourceLimit(
						name=resource_type,
						limit=max(
							(
								self._int_null(self._get_resource_plan_free(subscription.name, hs_resource_type, subscription_period)) + 
								self._int_null(self._get_resource_extra_package_free(subscription.name, hs_resource_type))
							), 
							getattr(subscription, resource_type)
						)
					))

				traffic_hs_resource_type = 121
				subscription_resource_limits.limits.append(ResourceLimit(
					name=ResourceType.TRAFFIC,
					limit=max(
						(
							self._int_null(self._get_resource_plan_free(subscription.name, traffic_hs_resource_type, subscription_period)) + 
							self._int_null(self._get_resource_extra_package_free(subscription.name, traffic_hs_resource_type))
						),
						self._int_null(self._get_resource_allocated(subscription.name, traffic_hs_resource_type))
					) * 1024 * 1024 # in H-Sphere traffic is stored in GB
				))

				# H-Sphere has two different ways to bill disk space: disk summary and disk quotas. Refer to H-Sphere documentation for more details. 
				# We migrate both of them to a single disk space limit.
				disk_summary_hs_resource_type = 4003
				disk_summary_free = self._get_resource_plan_free(subscription.name, disk_summary_hs_resource_type, subscription_period)
				if disk_summary_free is not None and disk_summary_free != '' and disk_summary_free != 0: # disk summary is used in H-Sphere (briefly - customer pays for average disk usage)
					logger.debug("Disk summary is used for subscription '%s'", subscription.name)
					disk_summary_free = self._int_null(disk_summary_free)
					subscription_resource_limits.limits.append(ResourceLimit(
						name=ResourceType.DISK_SPACE,
						limit=max(
							(
								disk_summary_free + 
								self._int_null(self._get_resource_extra_package_free(subscription.name, disk_summary_hs_resource_type))
							),
							self._int_null(self._get_resource_allocated(subscription.name, disk_summary_hs_resource_type))
						) * 1024 # in H-Sphere disk summary is stored in MB
					))
				else: # disk quota is used in H-Sphere (briefly - customer pays for hard disk quota limit that can not be exceeded)
					disk_quota_hs_resource_type = 4001
					logger.debug("Disk quota is used for subscription '%s'", subscription.name)
					subscription_resource_limits.limits.append(ResourceLimit(
						name=ResourceType.DISK_SPACE,
						limit=max(
							(
								self._int_null(self._get_resource_plan_free(subscription.name, disk_quota_hs_resource_type, subscription_period)) + 
								self._int_null(self._get_resource_extra_package_free(subscription.name, disk_quota_hs_resource_type))
							),
							self._int_null(self._get_resource_allocated(subscription.name, disk_quota_hs_resource_type))
						) * 1024 # in H-Sphere disk quota is stored in MB
					))

		return model

	def _get_resource_plan_free(self, subscription, resource_id, period):
		query_template = """
		SELECT plan_value.value
			FROM domains
			JOIN parent_child pc
				ON pc.child_id = domains.id
			JOIN accounts
				ON accounts.id = pc.account_id
			JOIN plan_value
				ON plan_value.plan_id = accounts.plan_id
			WHERE
				domains.name = '%s'
				AND
				plan_value.name = '_FREE_UNITS_{period}'
				AND
				plan_value.type_id = %s 
		""" % (subscription, resource_id,)
		period_value = self._fetch_single_value(query_template.format(period=period))
		base_value = self._fetch_single_value(query_template.format(period=''))
		return period_value or base_value

	def _get_resource_extra_package_free(self, subscription, resource_id):
		query = """
		SELECT SUM(ep_entry.free)
			FROM domains
			JOIN parent_child pc
				ON pc.child_id = domains.id
			JOIN accounts
				ON accounts.id = pc.account_id
			JOIN account_ep
				ON account_ep.account_id = accounts.id
			JOIN extra_pack
				ON extra_pack.id = account_ep.ep_id
			JOIN ep_entry
				ON ep_entry.ep_id = extra_pack.id
			WHERE
				domains.name = '%s'
				AND
				ep_entry.type_id = %s
		""" % (subscription, resource_id,)
		return self._fetch_single_value(query, 0)

	def _get_resource_allocated(self, subscription, resource_id):
		query = """
		SELECT resource_amount.amount
			FROM domains
			JOIN parent_child pc
				ON pc.child_id = domains.id
			JOIN accounts
				ON accounts.id = pc.account_id
			JOIN parent_child account_pc
				ON account_pc.account_id = accounts.id 
			JOIN resource_amount
				ON resource_amount.id = account_pc.child_id
			WHERE
				domains.name = '%s'
				AND
				account_pc.child_type = %s
		""" % (subscription, resource_id)
		return self._fetch_single_value(query, 0)

	def _get_subscription_period(self, subscription_name):
		query = """
			SELECT a.period_id
			FROM accounts a
			JOIN parent_child pc_domains ON pc_domains.account_id = a.id
			JOIN domains d ON pc_domains.child_id = d.id
			WHERE d.name = '%s'
		""" % subscription_name
		return self._fetch_single_value(query, '')

	def _int_null(self, value):
		if value is None:
			return 0
		else:
			return int(value)

	def _fetch_single_value(self, query, default=None):
		self.conn.execute(query)
		row = self.conn.fetchone()
		if row is None or len(row) == 0:
			return default
		else:
			return row[0]

class Importer:
	def __init__(self, poa_api, try_subscription):
		self.poa_api = poa_api
		self.try_subscription = try_subscription

	def import_resource_limits(self, model):
		poa_resclass_names = {
			ResourceType.DOMAIN: 'plesk_integration.domains_number',
			ResourceType.SUBDOMAIN: 'plesk_integration.subdomains_number',
			ResourceType.MAILBOX: 'plesk_mail.mailboxes',
			ResourceType.DISK_SPACE: 'disc_space',
			ResourceType.TRAFFIC: 'traffic',
		}
		for subscription in model.subscriptions:
			with self.try_subscription(subscription.name, u"Failed to restore resource limits."):
				for limit in subscription.limits:
					logger.debug("Set '%s' limit of subscription '%s' to '%s' in POA", limit.name, subscription.name, limit.limit)
					self._set_resource_limits(subscription.name, poa_resclass_names[limit.name], limit.limit)

	def _set_resource_limits(self, subscription, resclass_name, limit):
		webspace_id = self.poa_api.get_webspace_id_by_primary_domain(subscription)
		subscription_id = self.poa_api.get_webspace(webspace_id)['sub_id']

		rt_ids = [r for r in self.poa_api.getSubscription(subscription_id, get_resources=True).resources if r.resclass_name == resclass_name]
		if len(rt_ids) == 0:
			return
		rt_id = rt_ids[0].rt_id
		self.poa_api.set_resource_type_limits(
			subscription_id=subscription_id,
			limits=[{'resource_type_id': rt_id, 'limit': str(limit)}]
		)

