"""Classes and routines for composing different reports
(pre- and post-migration checks, migration final report, etc)
"""

from itertools import chain
from collections import OrderedDict
from parallels.core.utils.common import default
from parallels.core.utils.entity import Entity


class Report(Entity):
	def __init__(self, type, name, issues=None, children=None):
		self.type = type
		self.name = name
		self.issues = issues if issues is not None else []
		self.children = children if children is not None else OrderedDict()

	def subtarget(self, type, name):
		if (type, name) not in self.children:
			self.children[(type, name)] = Report(type, name)
		return self.children[(type, name)]

	def add_issue(self, *args, **kw):
		issue = Issue(*args, **kw)
		self.add_issue_obj(issue)

	def add_issue_obj(self, issue):
		if issue not in self.issues:
			self.issues.append(issue)

	def has_errors(self):
		return self._has_issue_of_severity(Problem.ERROR)

	def has_warnings(self):
		return self._has_issue_of_severity(Problem.WARNING)

	def has_errors_or_warnings(self):
		return any(chain(
			[
				issue.problem.severity == Problem.ERROR or issue.problem.severity == Problem.WARNING
				for issue in self.issues
			],
			[subreport.has_errors_or_warnings() for subreport in self.children.values()]
		))

	def has_issues(self):
		return any(chain(
			[len(self.issues) > 0],
			[subreport.has_issues() for subreport in self.children.values()]
		))

	def _has_issue_of_severity(self, severity):
		return any(chain(
			[issue.problem.severity == severity for issue in self.issues],
			[subreport.has_errors() for subreport in self.children.values()]
		))

	def as_dictionary(self):
		return {
			'type': default(self.type, ''),
			'name': default(self.name, ''),
			'issues': [issue.as_dictionary() for issue in self.issues],
			'children': [child.as_dictionary() for child in self.children.itervalues()]
		}

	def __eq__(self, other):
		return all([
			self.type == other.type,
			self.name == other.name,
			self.children == other.children,
			self.issues == other.issues
		])

	def __repr__(self):
		return u'Report(type=%r, name=%r, children=%r, issues=%r)' % (self.type, self.name, self.children, self.issues)


class Problem(Exception):
	INFO = 'info'
	WARNING = 'warning'
	ERROR = 'error'

	def __init__(self, key, severity, description):
		self.key = key
		self.severity = severity
		self.description = description

	def as_dictionary(self):
		return {
			'key': default(self.key, ''),
			'severity': self.severity,
			'description': self.description
		}

	def __repr__(self):
		return u'Problem(%r, %r, %r)' % (self.key, self.severity, self.description)

	def __eq__(self, other):
		return (
			self.key == other.key and self.severity == other.severity
			and self.description == other.description
		)


class Issue(object):
	def __init__(self, problem, solution):
		self.problem = problem
		self.solution = solution

	def as_dictionary(self):
		return {
			'problem': self.problem.as_dictionary(),
			'solution': self.solution
		}

	def __repr__(self):
		return u'Issue(%r, %r)' % (self.problem, self.solution)

	def __eq__(self, other):
		return self.problem == other.problem and self.solution == other.solution


class SubscriptionReportProvider(object):
	"""Interface to get reports for each subscription by source server and subscription name"""
	def get_root_report(self):
		raise NotImplementedError()

	def get_subscription_report(self, server_id, name):
		"""
		:type server_id: basestring | None
		:type name: basestring
		:rtype: parallels.core.checking.Report
		"""
		raise NotImplementedError()


class PlainReport(SubscriptionReportProvider):
	"""Utility wrapper around Report, allowing to add issues for a subscription or a customer
	without knowing their parents.
	"""
	def __init__(self, root_report, customers, subscriptions, servers=None):
		self.root_report = root_report
		if servers is not None:
			self.servers = servers
		else:
			self.servers = {}
		self.customers = customers
		self.subscriptions = subscriptions

	def get_root_report(self):
		return self.root_report

	def get_server_report(self, server_id):
		server_type = self.servers.get(server_id, 'Source')
		return self.root_report.subtarget(u"%s server" % server_type, server_id)

	def add_server_issue(self, server_id, *args, **kw):
		server_report = self.get_server_report(server_id)
		server_report.add_issue(*args, **kw)

	def get_reseller_report(self, server_id, login):
		server_report = self.get_server_report(server_id)
		return server_report.subtarget('Reseller', login)

	def add_reseller_issue(self, server_id, login, *args, **kw):
		reseller_report = self.get_reseller_report(server_id, login)
		reseller_report.add_issue(*args, **kw)

	def get_customer_report(self, server_id, login):
		report = self.get_server_report(server_id)
		for parent in self.customers.get(server_id, {}).get(login, []):
			report = report.subtarget(parent.type, parent.name)
		return report.subtarget('Client', login)

	def add_customer_issue(self, server_id, login, *args, **kw):
		customer_report = self.get_customer_report(server_id, login)
		customer_report.add_issue(*args, **kw)

	def get_subscription_report(self, server_id, name):
		"""
		:type server_id: basestring | None
		:type name: basestring
		:rtype: parallels.core.checking.Report
		"""
		report = self.get_server_report(server_id)
		for parent in self.subscriptions.get(server_id, {}).get(name, []):
			report = report.subtarget(parent.type, parent.name)
		return report.subtarget('Subscription', name)

	def add_subscription_issue(self, server_id, name, *args, **kw):
		subscription_report = self.get_subscription_report(server_id, name)
		subscription_report.add_issue(*args, **kw)
