from collections import namedtuple

from parallels.core import APIError
from parallels.core.utils.common import obj
from parallels.core.utils.common.xml import text_elem, seq, elem
from parallels.plesk.utils.xml_rpc.plesk import messages

PleskErrorCode = obj(
	USER_ACCOUNT_ALREADY_EXISTS=1002,
	USER_ROLE_ALREADY_EXISTS=1007,
	OBJECT_DOES_NOT_EXIST=1013,
	OWNER_DOES_NOT_EXIST=1015,
	PASSWORD_IS_TOO_SHORT=1019,
)


class PleskError(APIError):
	"""Error occured when executing request to Plesk API"""
	def __init__(self, code, message):
		super(PleskError, self).__init__(u"Plesk error [%d]: %s" % (code, message))
		self.code = code
		self.message = message

	@property
	def how_to_reproduce(self):
		"""User-friendly description on how to reproduce API request"""
		return messages.HOW_TO_REPRODUCE_FAILED_REQUEST.format(error_message=self.message)


class Result(object):
	@staticmethod
	def parse(elem, parse_data = lambda e: None):
		status = elem.findtext('status')
		if status == 'ok':
			return Success(parse_data(elem))
		else:
			code = int(elem.findtext('errcode'))
			message = elem.findtext('errtext')
			return Failure(code, message)


class Success(namedtuple('Success', ('data',))):
	@property
	def ok(self):
		return True

	def check(self):
		pass


class Failure(namedtuple('Failure', ('code', 'message'))):
	@property
	def ok(self):
		return False

	def check(self):
		raise PleskError(self.code, self.message)
	
	@property
	def data(self):
		self.check()
		return None


def parse_result_set(elem, parse_function, data_indicator):
	result_nodes = elem.findall('result')
	
	# for each filter, when there are no objects matching filter,
	# Plesk API returns result node with status = ok, but without data
	# discard such nodes
	return [
		Result.parse(r, parse_function)
		for r in result_nodes if r.findtext('status') != 'ok' or r.find(data_indicator) is not None
	]


def filter_owner_not_found_error(result_set):
	"""Plesk error 1015 Owner not found is returned when objects not found by owner.
	Until it is fixed in Plesk, filter this error out"""
	return [
		result for result in result_set 
		if not (isinstance(result, Failure) and result.code == PleskErrorCode.OWNER_DOES_NOT_EXIST)
	]


def filter_object_not_found_error(result_set):
	"""Filter out results with "1013 owner does not exist" error

	It is not actually an error, but an indicator that no item matched filter.
	"""
	return [
		result for result in result_set
		if not (isinstance(result, Failure) and result.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST)
	]


def get_result_set_data(result_set):
	"""Get list of API response data, checking whether response was successful"""
	return [
		result.data for result in result_set
	]


def operation(name, fields):
	class Operation(namedtuple(name, fields)):
		def should_be_skipped(self):
			return False

	return Operation


def operation_with_filter(name, fields=tuple()):
	"""Create base class for operation with filter.

	Usage:
		class Get(core.operation_with_filter('Get', ('name', 'smth'))):
			pass

	Read comments in `Client.send_many` to understand why
	operations with filter are special.
	"""
	class Operation(namedtuple(name, ('filter',) + fields)):
		def should_be_skipped(self):
			return self.filter.is_empty()

		def fake_response(self):
			return []

		def inner_xml(self):
			if self.should_be_skipped():
				return []
			else:
				return seq(
					elem('filter', self.filter.inner_xml()),
					*self.data_xml()
				)
		def data_xml(self):
			return []

	return Operation


# FilterAll for some operators is implemented in a different way than FilterAll for other operators
# In most cases it is "<filter/>", while in some special cases it is "<filter><all/></filter>"
# See Plesk API schema to understand whether you should use FilterAll or FilterAllWithNode


class FilterAll:
	def inner_xml(self):
		return []

	def is_empty(self):
		return False

	def __repr__(self):
		return "FilterAll()"

	def __eq__(self, another):
		return isinstance(another, FilterAll)


class FilterAllWithNode(object):
	def inner_xml(self): 
		return [elem('all')]

	def is_empty(self):
		return False


def declare_filter(name, node_name):
	"""Create filter class.

	Usage:
		FilterBySiteId = declare_filter('FilterBySiteId', 'site-id')
	"""
	class Filter:
		def __init__(self, ids):
			self.ids = ids

		def inner_xml(self):
			return [text_elem(node_name, rid) for rid in self.ids]

		def is_empty(self):
			return len(self.ids) == 0

		def __repr__(self):
			return "%s(%r)" % (name, self.ids)

		def __eq__(self, other):
			return type(self) == type(other) and self.ids == other.ids

	return Filter

