from collections import namedtuple
from textwrap import dedent

from parallels.common.config_utils import \
	ConfigSection, read_http_connection_settings, read_auth, \
	HttpConnectionSettings
from parallels.utils import obj
from parallels.utils import is_empty
from parallels.utils.ip import is_ipv6

from . import MigrationError, MigrationConfigurationFileError

MailContent = obj(
	NONE='none',
	MESSAGES='messages',
	FULL='full',
)

MailImapEncryption = obj(
	NONE='none',
	SSL='ssl',
	TLS='tls'
)

class ExternalDBConfig(namedtuple('ExternalDBConfig', ('id', 'host', 'ip', 'ssh_auth'))):
	@property
	def is_windows(self): return False

CopyMailContentSettings = namedtuple('CopyMailContentSettings', (
	# value of MailContent 
	'mode',
	# value of MailImapEncryption
	'mail_imap_encryption', 
	# custom command as a string - see common.mail for details
	'custom_mail_copy_content_command',
	# imapsync option - see common.mail.imapsync for details
	'mailbox_name_separator',
	# imapsync option - see common.mail.imapsync for details
	'mailbox_name_prefix'
))

def read_copy_mail_content_settings(section, is_windows):
	if is_windows:
		cmc_allowed_values = [
			MailContent.FULL, MailContent.MESSAGES, MailContent.NONE
		]
	else:
		cmc_allowed_values = [
			MailContent.FULL, MailContent.NONE
		]

	cmc = section.get('copy-mail-content', MailContent.FULL)
	copy_mail_content = cmc.lower()

	if copy_mail_content not in cmc_allowed_values:
		raise MigrationError(
			u"Invalid value '%s' is specified for 'copy-mail-content' "
			u"setting in '%s' section of tool's configuration file, "
			"should be one of: %s (case-insensitive)." 	% (
				copy_mail_content, section, ",".join(cmc_allowed_values)
			)
		)

	custom_mail_copy_content_command = section.get(
		'custom-mail-copy-content-command', None
	)

	mail_imap_encryption_allowed_values = [
		MailImapEncryption.NONE, MailImapEncryption.SSL, MailImapEncryption.TLS
	]
	mail_imap_encryption = section.get(
		'imap-encryption', MailImapEncryption.NONE
	).lower()
	if mail_imap_encryption not in mail_imap_encryption_allowed_values:
		raise MigrationError(
			u"Invalid value '%s' is specified for 'imap-encryption' "
			u"setting in '%s' section of tool's configuration file, "
			"should be one of: %s (case-insensitive)." 	% (
				mail_imap_encryption, section.section_name, 
				", ".join(mail_imap_encryption_allowed_values)
			)
		)

	mailbox_name_prefix = section.get('mailbox-name-prefix', None)
	mailbox_name_separator = section.get('mailbox-name-separator', None)

	return CopyMailContentSettings(
		mode=copy_mail_content, mail_imap_encryption=mail_imap_encryption,
		custom_mail_copy_content_command=custom_mail_copy_content_command,
		mailbox_name_separator=mailbox_name_separator,
		mailbox_name_prefix=mailbox_name_prefix,
	)

WindowsAgentSettings = namedtuple('WindowsAgentSettings', (
	'enabled', 'use_ssl', 'port', 'client_cert', 'client_key', 'server_cert')
)

class SourcePleskUnixConfig(namedtuple('SourcePleskUnixConfig', (
		'id', 'ip', 'ssh_auth', 'plesk_api', 'session_dir', 'mail_settings',
	))):
	@property
	def is_windows(self): return False

class SourcePleskWindowsConfig(namedtuple('SourcePleskWindowsConfig', (
		'id', 'ip', 'windows_auth', 'plesk_api', 'session_dir', 'mail_settings', 'agent_settings'
	))):
	@property
	def is_windows(self): return True

class PpaConfig(namedtuple('PpaConfig', ('ip', 'ssh_auth', 'poa_api', 'plesk_api', 'unix_session_dir', 'windows_session_dir'))):
	@property
	def is_local(self):
		"""If target server is local or remote
		
		Local server means that migrator works on the target node, remote means
		that migrator's node and target node are different servers
		"""
		 # always false, local execution on Unix target node is not supported
		 # yet, we always execute commands by SSH
		return False

class TargetPleskUnixConfig(namedtuple('TargetPleskUnixConfig', ('ip', 'ssh_auth', 'plesk_api', 'unix_session_dir', 'windows_session_dir'))):
	@property
	def is_windows(self): 
		return False

	@property
	def is_local(self):
		"""If target server is local or remote
		
		Local server means that migrator works on the target node, remote means
		that migrator's node and target node are different servers
		"""
		 # always false, local execution on Unix target node is not supported
		 # yet, we always execute commands by SSH
		return False

class TargetPleskWindowsConfig(namedtuple('TargetPleskWindowsConfig', ('ip', 'windows_auth', 'plesk_api', 'unix_session_dir', 'windows_session_dir', 'agent_settings'))):
	@property
	def is_windows(self):
		return True

	@property
	def is_local(self):
		"""If target server is local or remote
		
		Local server means that migrator works on the target node, remote means
		that migrator's node and target node are different servers
		"""
		return self.windows_auth.username is None

POA_URL_DEFAULTS = dict(
	protocol = 'https',
	port = 8440,
	path = '/RPC2'
)

PLESK_URL_DEFAULTS = dict(
	protocol = 'https',
	port = 8443,
	path = '/enterprise/control/agent.php'
)

def read_windows_agent_settings(config, section_name):
	section = ConfigSection(config, section_name)
	return WindowsAgentSettings(
		enabled=section.get('windows-agent-enabled', False),
		port=section.get('windows-agent-port', 10155),
		use_ssl=section.get('windows-agent-use-ssl', False),
		client_cert=section.get('windows-agent-client-cert', None), 
		client_key=section.get('windows-agent-client-key', None), 
		server_cert=section.get('windows-agent-server-cert', None)
	)
	
def read_plesk_connection_settings(config, section_name):
	"""Read Plesk API connection settings.
	"""
	section = ConfigSection(config, section_name)

	ip = section['ip']

	api_defaults = dict(PLESK_URL_DEFAULTS,
		host = ip,
		username = section['panel-username'],
		password = section['panel-password'],
	)
	return read_http_connection_settings(
		section.prefixed('plesk-').with_defaults(api_defaults)
	)

def read_source_dns_servers_settings(config, section_name):
	"""Read centralized dns ip addresses and credentials.
	"""
	section = ConfigSection(config, section_name)

	ip = section['ip']
	ssh_auth = read_ssh_auth(section)
	return SourcePleskUnixConfig(section_name, ip, ssh_auth, "false", "false", None)

def read_source_plesk_settings(config, section_name):
	"""Read Plesk API and SSH connection settings.
	"""
	section = ConfigSection(config, section_name)

	ip = section['ip']
	os = section['os']
	session_dir = section.get('session-dir', 'c:\\migrator' if os == 'windows' else '/tmp')

	is_windows = (os == 'windows')
	mail_settings = read_copy_mail_content_settings(section, is_windows)

	plesk_api = read_plesk_connection_settings(config, section_name)
	if os == 'windows':
		windows_auth = _read_windows_auth(section)
		return SourcePleskWindowsConfig(
			section_name, ip, windows_auth, plesk_api, session_dir,
			mail_settings, read_windows_agent_settings(config, section_name)
		)
	else:
		ssh_auth = read_ssh_auth(section)
		return SourcePleskUnixConfig(
			section_name, ip, ssh_auth, plesk_api, session_dir, mail_settings
		)

def read_target_plesk_settings(config, section_name):
	section = ConfigSection(config, section_name)
	ip = section['ip']
	os = section['os']
	plesk_settings = dict(
		host=ip,
		username=config.get(section_name, 'panel-username'),
		password=config.get(section_name, 'panel-password'),
	)
	plesk_api = read_http_connection_settings(section.prefixed('plesk-').with_defaults(dict(PLESK_URL_DEFAULTS, **plesk_settings)))
	unix_session_dir = section.get('unix-session-dir', '/tmp')
	windows_session_dir = section.get('windows-session-dir', 'C:\\migrator')
	if os == 'windows':
		windows_auth = _read_windows_auth(section, required=False)
		return TargetPleskWindowsConfig(
			ip=ip, windows_auth=windows_auth, plesk_api=plesk_api,
			unix_session_dir=unix_session_dir,
			windows_session_dir=windows_session_dir,
			agent_settings=read_windows_agent_settings(config, section_name)
		)
	else:
		ssh_auth = read_ssh_auth(section)
		return TargetPleskUnixConfig(ip=ip, ssh_auth=ssh_auth, plesk_api=plesk_api, unix_session_dir=unix_session_dir, windows_session_dir=windows_session_dir) 

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

	ip = section['ip']
	if is_ipv6(ip):
		raise MigrationError('PPA API does not work over IPv6, so please change IPv6 (%s) to IPv4 in config file in PPA\'s section' % ip)
		
	ssh_auth = read_ssh_auth(section)

	ppa_settings = dict(
		host = ip,
		username = config.get(section_name, 'panel-username'),
		password = config.get(section_name, 'panel-password'),
	)
	poa_api = _read_poa_connection_settings(section.prefixed('poa-').with_defaults(dict(POA_URL_DEFAULTS, **ppa_settings)))
	plesk_api = read_http_connection_settings(section.prefixed('plesk-').with_defaults(dict(PLESK_URL_DEFAULTS, **ppa_settings)))
	unix_session_dir = section.get('unix-session-dir', '/tmp')
	windows_session_dir = section.get('windows-session-dir', 'C:\\migrator')

	return PpaConfig(ip, ssh_auth, poa_api, plesk_api, unix_session_dir, windows_session_dir)

def read_external_db_settings(config, section_name):
	section = ConfigSection(config, section_name)
	host = section['host']
	ip = section['ip']
	ssh_auth = read_ssh_auth(section)
	return ExternalDBConfig(id=section_name, host=host, ip=ip, ssh_auth=ssh_auth)

class SSHAuthPassword(namedtuple('SSHAuthPassword', ('username', 'password'))):
	def connect(self, ip, client):
		"""
		ip - server IP address
		client - instance of paramiko.SSHClient
		"""
		client.connect(
			ip,
			username=self.username, password=self.password,
			look_for_keys=False
		)

class SSHAuthKeyFilename(namedtuple('SSHAuthKeyFilename', ('username', 'key_filename'))):
	def connect(self, ip, client):
		"""
		ip - server IP address
		client - instance of paramiko.SSHClient
		"""
		client.connect(
			ip, username=self.username,
			look_for_keys=False, key_filename=self.key_filename
		)

class SSHAuthKeyDefault(namedtuple('SSHAuthKeyDefault', ('username',))):
	def connect(self, ip, client):
		"""
		ip - server IP address
		client - instance of paramiko.SSHClient
		"""
		client.connect(
			ip, username=self.username,
			look_for_keys=True
		)

def read_ssh_auth(section):
	from os.path import exists
	if section['ssh-auth-type'] == 'password':
		return SSHAuthPassword(username=section['ssh-username'], password=section['ssh-password'])
	elif section['ssh-auth-type'] == 'key':
		if 'ssh-key' in section:
			if section['ssh-key'] == '':
				return SSHAuthKeyDefault(username=section['ssh-username'])
			else:
				if exists(section['ssh-key']):
					return SSHAuthKeyFilename(username=section['ssh-username'], key_filename=section['ssh-key'])
				else:
					raise Exception(u"ssh-key file specified in section '%s' of configuration file does not exist" % (section.section_name,))
		else:
			return SSHAuthKeyDefault(username=section['ssh-username'])
	else:
		raise MigrationConfigurationFileError(u"Invalid value '%s' of 'ssh-auth-type' parameter in '[%s]' section of configuration file. Allowed values: 'password', 'key'" % (
			section['ssh-auth-type'], section.section_name,
		))

def is_transfer_resource_limits_enabled(config, target_panel):
	if 'transfer-resource-limits' in config.options('GLOBAL'):
		return config.getboolean('GLOBAL', 'transfer-resource-limits')
	else:
		default = target_panel.transfer_resource_limits_by_default()
		return default

def _read_windows_auth(section, required=True):
	auth = read_auth(section.prefixed('windows-'))

	if required and is_empty(auth.username):
		raise MigrationError(dedent("""\
			Windows username is not specified for server '%s'. Please
			specify it with 'windows-username' option of corresponding section
			'[%s]' in config.ini"""
		) % (section.section_name, section.section_name))
		
	if required and is_empty(auth.password):
		raise MigrationError(dedent("""\
			Windows password is not specified for server '%s'. Please
			specify it with 'windows-password' option of corresponding section
			'[%s]' in config.ini"""
		) % (section.section_name, section.section_name))

	return auth

def _read_poa_connection_settings(section):
	from urlparse import urlparse, urlunparse

	s = read_http_connection_settings(section)
	
	# Put username and password to URL.
	scheme, host, path, params, query, fragment = urlparse(s.url)
	host = '%s:%s@%s' % (s.auth.username, s.auth.password, host)
	url = urlunparse((scheme, host, path, params, query, fragment))

	return HttpConnectionSettings(url = url, auth = s.auth)
