import logging
import random 
import string
from collections import namedtuple

import parallels.utils 
from parallels.utils import cached
from parallels.common.utils import plesk_api_utils
from parallels.common.utils import migrator_utils
from parallels.common.import_api.import_api import ImportAPI 
from parallels.target_panel_plesk.import_api import model
from parallels.plesk_api import operator as plesk_ops
from parallels.plesk_api.core import PleskError, PleskErrorCode
from parallels.utils import unique_list

OWNER_ADMIN = 1

class PleskBaseImportAPI(ImportAPI):
	logger = logging.getLogger(__name__)
	system_name = 'PPA'

	def __init__(self, conn):
		self.conn = conn

	def has_addon_service_templates(self):
		return True

	def create_reseller(self, reseller):
		result = self.conn.plesk_api().send(
			plesk_ops.ResellerOperator.Add(
				gen_info=plesk_ops.ResellerAddGenInfo(
					cname=reseller.company, 
					pname=self.join_strs([reseller.personal_info.first_name, reseller.personal_info.last_name]), 
					passwd=reseller.password,
					login=reseller.login, 
					status=0, 
					phone=reseller.personal_info.primary_phone, 
					fax=reseller.personal_info.fax, 
					email=reseller.personal_info.email, 
					address=self.join_strs([reseller.personal_info.address_line_1, reseller.personal_info.address_line_2], delim='\n'),
					city=reseller.personal_info.city, 
					state=reseller.personal_info.state, 
					pcode=reseller.personal_info.postal_code, 
					country=reseller.personal_info.country_code, 
					locale=None, 
					external_id=None
				),
				limits=plesk_ops.ResellerAddLimits(
					limits=[
						plesk_ops.ResellerAddLimit(limit_name, limit_value)	
						for limit_name, limit_value in reseller.settings.limits.iteritems()
					],
					resource_policy=plesk_ops.ResellerAddResourcePolicy(
						overuse=reseller.settings.overuse,
						oversell=reseller.settings.oversell,
					) if (
						reseller.settings.overuse is not None 
						and 
						reseller.settings.oversell is not None
					) else None
				) if reseller.settings is not None else None,
				permissions=[
					plesk_ops.ResellerAddPermission(permission_name, permission_value)
					for permission_name, permission_value in reseller.settings.permissions.iteritems()
				] if reseller.settings is not None else None
			)
		)
		
		reseller_id = result.data.reseller_id
		
		self.conn.plesk_api().send(
			plesk_ops.ResellerOperator.IppoolAddIp(
				reseller_id=reseller_id, 
				ip_address=self.conn.main_node_ip, 
				ip_type='shared'
			)
		).check()

		return reseller_id

	def list_webspaces(self, domain_names):
		webspaces = []
		for domain_name in unique_list(domain_names):
			try:
				webspace_info = self.conn.plesk_api().send(
					plesk_ops.SubscriptionOperator.Get(
						dataset=[plesk_ops.SubscriptionOperator.Dataset.GEN_INFO],
						filter=plesk_ops.SubscriptionOperator.FilterByName([domain_name])
					)
				)[0].data
			except PleskError as e:
				if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST: # webspace with such name does not exist
					continue
				else:
					raise

			webspace_id = webspace_info[0]
			gen_info = webspace_info[1].gen_info 
			webspace = model.PleskWebspaceInfo(
				webspace_id=webspace_id,
				name=gen_info.name,
				# in Plesk, webspace and subscription are the same entities, so subscription id is equal to webspace id
				subscription_id=webspace_id, 
				owner_id=gen_info.owner_id,
				status=model.WebspaceStatus.ACTIVE if gen_info.status == 0 else model.WebspaceStatus.SUSPENDED, 
			)
			webspaces.append(webspace)

		return webspaces

	def list_webspaces_brief(self, domain_names):
		webspaces = []
		for domain_name in unique_list(domain_names):
			try:
				webspace_info = self.conn.plesk_api().send(
					plesk_ops.SubscriptionOperator.Get(
						dataset=[plesk_ops.SubscriptionOperator.Dataset.GEN_INFO],
						filter=plesk_ops.SubscriptionOperator.FilterByName([domain_name])
					)
				)[0].data
			except PleskError as e:
				if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST: # webspace with such name does not exist
					continue
				else:
					raise

			webspace = model.PleskWebspaceInfoBrief(
				webspace_id=webspace_info[0], 
				name=webspace_info[1].gen_info.name
			)
			webspaces.append(webspace)

		return webspaces

	def list_resellers(self, reseller_logins):
		resellers = []
		for reseller_login in unique_list(reseller_logins):
			try:
				reseller_info = self.conn.plesk_api().send(
					plesk_ops.ResellerOperator.Get(
						dataset=[plesk_ops.ResellerOperator.Dataset.GEN_INFO],
						filter=plesk_ops.ResellerOperator.FilterByLogin([reseller_login])
					)
				)[0].data
			except PleskError as e:
				if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST: # reseller with such login does not exist
					continue
				else:
					raise

			gen_info = reseller_info[1].gen_info
			contact = model.PleskContactInfo(
				username=gen_info.login, email=gen_info.email,
				first_name=gen_info.pname, last_name=''
			)
			reseller = model.PleskResellerInfo(
				id=reseller_info[0],
				contact=contact,
			)
			resellers.append(reseller)

		return resellers

	def get_service_template_list(self, owner_id, active=None):
		plans = [
			result.data for result in self.conn.plesk_api().send(
				plesk_ops.ServicePlanOperator.Get(
					filter=plesk_ops.ServicePlanOperator.FilterAll(),
					owner_id=None if owner_id == OWNER_ADMIN else owner_id,
					owner_all=None
				)
			)
		]
		return [
			model.PleskServiceTemplateInfo(
				st_id=plan.plan_id, name=plan.name, owner_id=owner_id
			)
			for plan in plans
		]

	def get_addon_service_template_list(self, owner_id, active=None):
		plans = [
			result.data for result in self.conn.plesk_api().send(
				plesk_ops.ServicePlanAddonOperator.Get(
					filter=plesk_ops.ServicePlanAddonOperator.FilterAll(),
					owner_id=None if owner_id == OWNER_ADMIN else owner_id,
				)
			)
		]
		return [
			model.PleskServiceTemplateInfo(
				# we use guid as when adding addon plan to subscription, 
				# Plesk API requires either guid or external ID, 
				# but there is no way to use just plan ID
				st_id=plan.guid, 
				name=plan.name, owner_id=owner_id
			)
			for plan in plans
		]

	def get_service_template_info_list(self, service_template_ids):
		plans = [
			result.data for result in self.conn.plesk_api().send(
				plesk_ops.ServicePlanOperator.Get(
					filter=plesk_ops.ServicePlanOperator.FilterById(service_template_ids),
					owner_id=None, owner_all=True
				)
			)
		]
		return [ 
			model.PleskServiceTemplateDetails(
				st_id=plan.plan_id, 
				name=plan.name, 
				owner_id=plan.owner_id if plan.owner_id is not None else OWNER_ADMIN,
			)
			for plan in plans
		]
	
	def list_customers(self, customer_logins):
		accounts = []
		for login in unique_list(customer_logins):
			try:
				customer_info = self.conn.plesk_api().send(
					plesk_ops.CustomerOperator.Get(
						dataset=[plesk_ops.CustomerOperator.Dataset.GEN_INFO],
						filter=plesk_ops.CustomerOperator.FilterByLogin([login])
					)
				)[0].data[1]
			except PleskError as e:
				if e.code == PleskErrorCode.OBJECT_DOES_NOT_EXIST: # customer with such login does not exist
					continue
				else:
					raise

			contact = model.PleskContactInfo(
				username=customer_info.login, email=customer_info.email,
				first_name=customer_info.pname, last_name=''
			)
			account = model.PleskCustomerInfo(id=customer_info.id, contact=contact)
			accounts.append(account)

		return accounts

	def list_hosting_subscriptions(self, account_ids):
		subscriptions = []
		for account_id in unique_list(account_ids):
			api_response = self.conn.plesk_api().send(
				plesk_ops.SubscriptionOperator.Get(
					dataset=[plesk_ops.SubscriptionOperator.Dataset.GEN_INFO, plesk_ops.SubscriptionOperator.Dataset.SUBSCRIPTIONS],
					filter=plesk_ops.SubscriptionOperator.FilterByOwnerId([
						# '1' means admin's subscriptions
						account_id if account_id is not None else '1'
					])
				)
			)
			for subscription_response in api_response:
				try:
					subscription_info = subscription_response.data
				except PleskError as e:
					if e.code == PleskErrorCode.OWNER_DOES_NOT_EXIST: # owner with such id does not exist
						continue
					else:
						raise

				if len(subscription_info[1].subscriptions.subscriptions) > 0:
					plan_guid = subscription_info[1].subscriptions.subscriptions[0].plan.guid
					plan_id = self.conn.plesk_api().send(
						plesk_ops.ServicePlanOperator.Get(
							filter=plesk_ops.ServicePlanOperator.FilterByGuid([plan_guid]),
							owner_id=None,
							owner_all=True
						)
					)[0].data.plan_id
				else: # custom subscription - not assigned to any plan
					plan_id = None

				gen_info = subscription_info[1].gen_info 
				subscription = model.PleskSubscriptionInfo(
					subscription_id=subscription_info[0],
					owner_id=account_id,
					st_id=plan_id, 
					name=gen_info.name,
					is_active=bool(gen_info.status == 0),
				)
				subscriptions.append(subscription)

		return subscriptions

	def create_customer(self, owner_id, customer):
		result = self.conn.plesk_api().send(
			plesk_ops.CustomerOperator.Add(
				plesk_ops.CustomerAddInfo(
					gen_info=plesk_ops.CustomerAddGenInfo(
						cname=customer.company,
						pname=self.join_strs([
							customer.personal_info.first_name,
							customer.personal_info.last_name
							]),
						login=customer.login,
						passwd=customer.password,
						email=customer.personal_info.email,
						owner_id=None if owner_id == OWNER_ADMIN else owner_id,
						address=customer.personal_info.address_line_1, 
						city=customer.personal_info.city, 
						state=customer.personal_info.state, 
						country=customer.personal_info.country_code, 
						pcode=customer.personal_info.postal_code, 
						phone=customer.personal_info.primary_phone,
						fax=customer.personal_info.fax,
						locale=customer.personal_info.locale,
					)
				)
			)
		)
		return result.data.customer_id

	def create_hosting_subscription(self, account_id, plan_id, addon_plan_ids, subscription):
		# '1' means admin's subscriptions
		owner_id = account_id if account_id is not None else '1'
		self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.Add(
				plesk_ops.SubscriptionAddInfo(
					gen_setup=plesk_ops.SubscriptionAddGenSetup(
						name=subscription.name,
						owner_id=owner_id,
						htype=plesk_ops.SubscriptionAddHostingType.VIRTUAL,
						ip_address=self.conn.main_node_ip
						),
					hosting=plesk_ops.SubscriptionHostingVirtual(
						ip_addresses=plesk_ops.Ips(v4=self.conn.main_node_ip, v6=None),
						properties=[('ftp_login', self.generate_random_login()), ('ftp_password', parallels.utils.generate_random_password())]
						),
					plan_id=plan_id,
				)
			)
		).check()

		for addon_plan_id in addon_plan_ids:
			self.conn.plesk_api().send(
				plesk_ops.SubscriptionOperator.AddSubscription(
					filter=plesk_ops.SubscriptionOperator.FilterByName([subscription.name]),
					plan_guid=addon_plan_id,
				)
			).check()

		# Clean up skeleton
		# It would be better to use option, like "-do-not-apply-skeleton", 
		# but there is no such option for API
		with self.conn.main_node_runner() as runner:
			migrator_utils.clean_up_vhost_dir(
				subscription.name, runner, self.conn.is_windows
			)

	def sync_subscription(self, subscription_name):
		self.conn.plesk_api().send(
			plesk_ops.SubscriptionOperator.SyncSubscription(
				filter=plesk_ops.SubscriptionOperator.FilterByName([subscription_name])
			)
		).check()

	def create_service_template(self, owner_login, plan_name, plan_settings):
		"""
		plan_settings - instance of PleskPlanSettings
		"""
		self.conn.plesk_api().send(
			plesk_ops.ServicePlanOperator.Add(
				name=plan_name,
				owner_login=owner_login,
				limits=plesk_ops.ServicePlanLimits(
					overuse=None,
					limits=[
						plesk_ops.ServicePlanLimit(name, value)
						for name, value in plan_settings.limits.iteritems()
					]
				),
				permissions=[
					plesk_ops.ServicePlanPermission(name, value)
					for name, value in plan_settings.permissions.iteritems()
				]
			)
		).check()

	def create_addon_service_template(self, owner_login, plan_name, plan_settings):
		"""
		plan_settings - instance of PleskPlanSettings
		"""
		self.conn.plesk_api().send(
			plesk_ops.ServicePlanAddonOperator.Add(
				name=plan_name,
				owner_login=owner_login,
				limits=[
					plesk_ops.ServicePlanAddonLimit(name, value)
					for name, value in plan_settings.limits.iteritems()
				],
				permissions=[
					plesk_ops.ServicePlanAddonPermission(name, value)
					for name, value in plan_settings.permissions.iteritems()
				]
			)
		).check()

	def get_domain_dns_server_ips(self, domain_name):
		# the only DNS server controlled by target Plesk is located on the same node as Plesk
		return [self.conn.main_node_ip] 

	def list_mail_users(self, domain_name):
		"""List mail users registered in panel for specified domain"""
		return plesk_api_utils.list_mail_users(self.conn.plesk_api(), domain_name)

	def list_databases(self, domain_name, database_type):
		"""List databases registered in panel for specified domain with users
		
		Arguments:
		- domain_name - name of domain to list databases
		- database_type - type of databases to filter ('mysql' or 'mssql' or
		  'postgresql')

		Returns:
			list of tuples, first element - database name, second element - 
			list of related database user logins
		"""
		return plesk_api_utils.list_databases(
			self.conn.plesk_api(), domain_name, database_type
		)

	def suspend_customer(self, account_id):
		"""Suspend a single customer"""
		self.conn.plesk_api().send(
			plesk_ops.CustomerOperator.Set(
				filter=plesk_ops.CustomerOperator.FilterById([account_id]),
				customer_set_info=plesk_ops.CustomerSetInfo(
					status=plesk_ops.CustomerStatus.DISABLED_BY_ADMIN
				)

			)
		)

	@staticmethod
	def generate_random_login():
		random_digits = "".join(random.choice(string.digits) for i in range(10))
		return "sub_%s" % (random_digits,)

	@cached
	def get_all_ips(self):
		return self.conn.plesk_api().send(plesk_ops.IpOperator.Get()).data

	@staticmethod
	def join_strs(strs, delim=' '):
		return delim.join([
			part for part in strs 
			if part is not None and part != ''
		])

PleskPlanSettings = namedtuple('PleskPlanSettings', ('limits', 'permissions',))
PleskResellerSettings = namedtuple('PleskResellerSettings', ('limits', 'permissions', 'overuse', 'oversell',))

