from parallels.plesk.source.expand import messages
import logging
from collections import namedtuple
from contextlib import contextmanager

from paramiko import AuthenticationException

import parallels.plesk.config as connections_config
from parallels.plesk.source.plesk.connections import PleskMigratorConnections
from parallels.plesk.utils.xml_rpc import expand as expand_api
from parallels.core.utils.config_utils import ConfigSection, read_http_connection_settings
from parallels.core.migrator_config import read_ssh_auth
from parallels.core.utils import ssh_utils
from parallels.core import MigrationError, MigrationNoContextError
from parallels.core import run_command
from parallels.plesk.utils.xml_rpc.expand.client import ExpandClientError
from parallels.plesk.utils.xml_rpc.expand.core import ExpandSystemError
from parallels.plesk.utils.xml_rpc.expand.operator import ClientOperator
from parallels.plesk.source.plesk.server import PleskSourceServer
from parallels.plesk.panel import Panel
from parallels.core.utils.common import cached
from parallels.core.utils.common import merge_dicts


logger = logging.getLogger(__name__)


class ExpandMigratorConnections(PleskMigratorConnections):
	def __init__(self, global_context, target_panel, migrator_server):
		super(ExpandMigratorConnections, self).__init__(
			global_context, target_panel, migrator_server
		)
		settings = read_expand_settings(global_context.config, 'expand')
		logger.info(messages.LOG_EXPAND_HOST, settings.ip)
		self.expand = ExpandConnections(settings)

		self._centralized_mail_servers = self._read_plesk_settings_from_config(
			global_context.config, 'centralized-mail-servers', optional=True
		)
		self._centralized_dns_servers = self._read_dns_settings_from_config(
			global_context.config, 'centralized-dns-servers'
		)
		self._check_config(target_panel)

	def get_information_servers(self):
		return self._source_plesks

	@contextmanager
	def get_expand_runner(self):
		with self.expand.ssh() as ssh:
			yield run_command.SSHRunner(
				ssh, 
				messages.EXPAND_MAIN_NODE_DESCRIPTION % (self.expand.conn_settings.ip)
			)

	@property
	@cached
	def expand_main_server(self):
		return ExpandMainServer(self)

	@cached
	def expand_centralized_dns_server(self, server_id):
		return ExpandCentralizedDNSServer(server_id, self._centralized_dns_servers[server_id])

	@cached
	def get_source_node(self, node_id):
		all_servers = merge_dicts(
			self.get_source_plesks(), 
			self.get_external_db_servers(),
			self.get_centralized_dns_servers(),
			self.get_centralized_mail_servers()
		)
		node_settings = all_servers[node_id]
		return PleskSourceServer(
			node_id, node_settings, self._migrator_server
		)

	def get_centralized_mail_servers(self):
		"""Get information about source centralized mail servers
		
		Returns dictionary {server_id: server_settings} with connection
		information.
		"""
		return self._centralized_mail_servers

	def get_centralized_dns_servers(self):
		"""Get information about source centralized DNS servers
		
		Returns dictionary {server_id: server_settings} with connection
		information.
		"""
		return self._centralized_dns_servers

	def check_source_servers(self):
		super(ExpandMigratorConnections, self).check_source_servers()
		self._check_expand()

	@staticmethod
	def _read_dns_settings_from_config(config, list_name):
		settings = {}
		if config.has_option('GLOBAL', list_name):
			list_str = config.get('GLOBAL', list_name)
			if list_str.strip() != '':
				source_sections = map(str.strip, list_str.split(','))
				for section_name in source_sections:
					settings[section_name] = connections_config.read_source_dns_servers_settings(config, section_name)
					logger.info(messages.LOG_CDNS_HOST, section_name, settings[section_name].ip)
				return settings
			else:
				return {}
		else:
			return {}

	def _check_config(self, target_panel):
		"""Check that migration configuration defined in config file is correct and supported by migrator"""
		if isinstance(target_panel, Panel):
			for cmail_server_id, cmail_server_settings in self._centralized_mail_servers.iteritems():
				if self.target.is_windows and not cmail_server_settings.is_windows:
					raise MigrationNoContextError(
						messages.CMAIL_MIGRATION_FROM_UNIX_TO_WINDOWS_NOT_SUPPORTED % (
							cmail_server_id
						)
					)

	def _check_expand(self):
		logger.debug(messages.DEBUG_CHECK_EXPAND_API_CONNECTION)
		try:
			self.expand.api().send(ClientOperator.Get(
				filter=ClientOperator.Get.FilterAll(),
				dataset=[ClientOperator.Get.Dataset.INFO, ClientOperator.Get.Dataset.PERSONAL_INFO]
			))
		except ExpandSystemError as e:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			if e.error_code == 1:
				raise MigrationError(
					messages.FAILED_TO_LOGIN_TO_EXPAND_BY_EXPAND_API)
			raise
		except (ExpandClientError, EnvironmentError) as err:
			logger.debug(messages.LOG_EXCEPTION, exc_info=err)
			raise MigrationError(
				messages.FAILED_TO_CONNECT_TO_EXPAND % (
					self.expand.conn_settings.expand_api.url, err
				)
			)
		logger.debug(messages.DEBUG_CHECK_EXPAND_SSH)
		try:
			self.expand.ssh()
		except AuthenticationException as e:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(
				messages.FAILED_TO_LOGIN_TO_EXPAND_BY_SSH)
		except Exception as err:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(
				messages.FAILED_TO_CONNECT_TO_EXPAND_SSH % err
			)


class ExpandMainServer(object):
	def __init__(self, conn):
		self._conn = conn

	@staticmethod
	def description():
		return messages.EXPAND_MAIN_SERVER_DESCRIPTION

	@contextmanager
	def runner(self):
		with self._conn.get_expand_runner() as runner:
			yield runner

	@property
	@cached
	def expand_api(self):
		return self._conn.expand.api()


class ExpandCentralizedDNSServer(object):
	def __init__(self, server_id, settings):
		self._server_id = server_id
		self._settings = settings

	@contextmanager
	def runner(self):
		with ssh_utils.connect(self._settings) as ssh:
			yield run_command.SSHRunner(ssh, self.description())

	def description(self):
		return messages.CDNS_SERVER_DESCRIPTION % (self._server_id, self._settings.ip)


EXPAND_URL_DEFAULTS = dict(
	protocol='https',
	port=8442,
	path='/webgate.php'
)

ExpandConfig = namedtuple('ExpandConfig', ('ip', 'ssh_auth', 'expand_api'))


def read_expand_settings(config, section_name):
	section = ConfigSection(config, section_name)

	ip = section['ip']
	ssh_auth = read_ssh_auth(section)

	expand_settings = dict(
		host=ip,
		username=config.get(section_name, 'panel-username'),
		password=config.get(section_name, 'panel-password'),
	)
	expand_api_settings = read_http_connection_settings(
		section.prefixed('plesk-').with_defaults(dict(EXPAND_URL_DEFAULTS, **expand_settings))
	)

	return ExpandConfig(ip=ip, ssh_auth=ssh_auth, expand_api=expand_api_settings)



class ExpandConnections(object):
	def __init__(self, conn_settings):
		self.conn_settings = conn_settings

	def api(self):
		return expand_api.Client(
			self.conn_settings.expand_api.url, 
			self.conn_settings.expand_api.auth.username,
			self.conn_settings.expand_api.auth.password, 
			'9.0'
		)

	def ssh(self):
		return ssh_utils.connect(self.conn_settings)
