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
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.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'
		)

	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)
			)

	@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_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 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 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)
			)

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:
	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)
