import logging
from collections import namedtuple
from contextlib import contextmanager

from paramiko import AuthenticationException

import parallels.common.migrator_config as connections_config
from parallels.plesks_migrator.connections import PleskMigratorConnections
from parallels import expand_api
from parallels.common.utils.config_utils import ConfigSection, read_http_connection_settings
from parallels.common.migrator_config import read_ssh_auth
from parallels.common.utils import ssh_utils
from parallels.common import MigrationError, MigrationNoContextError
from parallels.common import run_command
from parallels.expand_api.client import ExpandClientError
from parallels.expand_api.core import ExpandSystemError
from parallels.expand_api.operator import ClientOperator
from parallels.plesks_migrator.server import PleskSourceServer
from parallels.target_panel_plesk.panel import PleskTargetPanel
from parallels.utils import cached
from parallels.utils import merge_dicts


logger = logging.getLogger(__name__)


class ExpandMigratorConnections(PleskMigratorConnections):
	def __init__(self, config, target_panel, migrator_server):
		super(ExpandMigratorConnections, self).__init__(
			config, target_panel, migrator_server
		)
		settings = read_expand_settings(config, 'expand')
		logger.info("Expand host: %s", settings.ip)
		self.expand = ExpandConnections(settings)

		self._centralized_mail_servers = self._read_plesk_settings_from_config(
			config, 'centralized-mail-servers', optional=True
		)
		self._centralized_dns_servers = self._read_dns_settings_from_config(
			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, 
				"Expand main node ('%s')" % (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("Centralized DNS '%s' host: %s", 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, PleskTargetPanel):
			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(
						"Migration from Unix centralized mail server '%s' to Plesk for Windows is not supported. "
						"It is recommended to migrate such configuration to PPA instead of Plesk. "
						"If you want to proceed migration to Plesk anyway "
						"you should remove that centralized mail server "
						"from configuration file and then migrate it manually." % (
							cmail_server_id
						)
					)

	def _check_expand(self):
		logger.debug(u"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(u'Exception:', exc_info=e)
			if e.error_code == 1:
				raise MigrationError(
					u'Unable to login to Expand API service due to incorrect login/password specified in config file.\n'
					u"Set valid credentials in 'panel-username' and 'panel-password' options "
					u"in [expand] section in config file and re-run migration tool."
				)
			raise
		except (ExpandClientError, EnvironmentError) as err:
			logger.debug(u'Exception:', exc_info=err)
			raise MigrationError(
				u"Failed to connect to Expand to '%s': %s"
				% (self.expand.conn_settings.expand_api.url, err)
			)
		logger.debug(u"Check Expand SSH connection")
		try:
			self.expand.ssh()
		except AuthenticationException as e:
			logger.debug(u'Exception:', exc_info=e)
			raise MigrationError(
				u'Unable to login to Expand SSH service due to incorrect login/password specified in config file.\n'
				u"Set valid credentials in 'ssh-username' and 'ssh-password' options "
				u"in [expand] section in config file and re-run migration tool."
			)
		except Exception as err:
			logger.debug(u'Exception:', exc_info=err)
			raise MigrationError(
				u"Failed to connect to Expand SSH : %s" % err
			)


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

	@staticmethod
	def description():
		return 'Expand main server'

	@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 "source centralized DNS server '%s' ('%s')" % (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)
