from collections import namedtuple
from contextlib import contextmanager
import logging
import psycopg2

from parallels.utils import cached
from parallels.common.utils import ssh_utils
from parallels.common import run_command
from parallels.common.connections.connections import Connections
from parallels.common.utils.config_utils import ConfigSection
from parallels.common.migrator_config import read_ssh_auth
from parallels.common.migrator_config import _read_windows_auth
from parallels.common.migrator_config import read_copy_mail_content_settings
from parallels.common import MigrationError
from parallels.hsphere_migrator.server import HsphereSourceServer


logger = logging.getLogger(__name__)
DbSettings = namedtuple('DbSettings', ('host', 'name', 'port', 'user', 'password'))
MsSqlHostSettings = namedtuple('MsSqlHostSettings', ('ip', 'instance', 'username', 'password'))
WinexecSettings = namedtuple('WinexecSettings', ('ip', 'windows_auth'))


class HsphereConfigurationError(MigrationError):
	pass


class HsphereMigratorConnections(Connections):
	def __init__(self, config, target_panel):
		super(HsphereMigratorConnections, self).__init__(config, target_panel)
		self.hsphere = HsphereConnections(read_hsphere_settings(config, 'hsphere'))

	def get_information_servers(self):
		return {'hsphere': self.hsphere.conn_settings}

	@cached
	def get_source_node(self, node_id):
		return HsphereSourceServer(
			node_id,
			self.get_information_servers()[node_id]
		)

	@contextmanager
	def hsphere_cp_runner(self):
		with self.hsphere.ssh() as ssh:
			yield run_command.SSHRunner(
				ssh, 
				"H-Sphere control panel server '%s'" % (self.hsphere.conn_settings.ip,)
			)


class HsphereConfig(namedtuple('HsphereConfig', (
	'id', 'ip', 'ssh_auth', 'db', 'mail_settings',
	'session_dir', 'api_port',
))):
	@property
	def is_windows(self):
		return False


def read_iis_nodes_settings(config):
	global_section = ConfigSection(config, 'GLOBAL')
	iis_nodes_list = global_section.get('hsphere-iis-service-nodes')

	iis_nodes = {}
	if iis_nodes_list is not None:
		for node_name in iis_nodes_list.split(','):
			node_section = ConfigSection(config, node_name)
			iis_nodes[node_section['ip']] = WinexecSettings(
				ip=node_section['ip'],
				windows_auth=_read_windows_auth(node_section)
			)
	return iis_nodes


def read_mssql_nodes_settings(config):
	global_section = ConfigSection(config, 'GLOBAL')
	mssql_nodes_list = global_section.get('hsphere-mssql-service-nodes')

	mssql_nodes = {}
	if mssql_nodes_list is not None:
		for node_name in mssql_nodes_list.split(','):
			node_section = ConfigSection(config, node_name)
			mssql_nodes[node_section['ip']] = MsSqlHostSettings(
				ip=node_section['ip'],
				instance=None if len(node_section['instance']) == 0 else node_section['instance'],
				username=node_section['username'],
				password=node_section['password']
			)
	return mssql_nodes


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

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

	database = DbSettings(
		host=ip,
		name=config.get(section_name, 'db-name'),
		port=config.get(section_name, 'db-port'),
		user=config.get(section_name, 'db-user'),
		password=config.get(section_name, 'db-password')
	)
	
	api_port = section.get('api-port', '8180')

	mail_settings = read_copy_mail_content_settings(section, is_windows=False)

	return HsphereConfig(
		id='hsphere', ip=ip, ssh_auth=ssh_auth, db=database, 
		session_dir='/tmp', api_port=api_port,
		mail_settings=mail_settings
	)


class HsphereConnections:
	def __init__(self, conn_settings):
		self.conn_settings = conn_settings

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

	def _connect(self):
		try:
			return psycopg2.connect(
				database=self.conn_settings.db.name,
				user=self.conn_settings.db.user,
				password=self.conn_settings.db.password,
				host=self.conn_settings.db.host,
				port=self.conn_settings.db.port
			)
		except Exception as e:
			if "no pg_hba.conf entry for host" in str(e):
				logger.debug(u'Exception:', exc_info=e)
				raise HsphereConfigurationError("""Unable to connect to H-Sphere CP database: %s
Make sure that:
1. You have allowed access from PPA management node, in pg_hba.conf file on H-Sphere CP server.
2. You have restarted postgresql service on H-Sphere CP server after that.""" % e)
			else:
				raise
	
	def db(self):
		return Psycopg2Wrapper(self._connect().cursor())

	def db_connection(self):
		return PgConnWrapper(self._connect())


class PgConnWrapper():
	def __init__(self, connection):
		self.connection = connection

	def cursor(self):
		return Psycopg2Wrapper(self.connection.cursor())

	def commit(self):
		self.connection.commit()

	def rollback(self):
		self.connection.rollback()

	def close(self):
		self.connection.close()


class Psycopg2Wrapper():
	def __init__(self, connection_cursor):
		self.conn = connection_cursor
		self.sql = ""
	
	def execute(self, sql):
		logger.debug(u"Execute postgresql sql: '%s'." % sql)
		self.sql = sql
		import time
		t_before = time.time()	
		self.conn.execute(sql)
		t_after = time.time()
		if t_after - t_before > 0.1:
			logger.debug(u"SLOW QUERY: query took %s seconds: '%s'." % (t_after - t_before, sql))
	
	def fetchall(self):
		result = self.conn.fetchall()
		return self._filter_result(result, [])
	
	def fetchmany(self):
		result = self.conn.fetchmany()
		return self._filter_result(result, [])
	
	def fetchone(self):
		result = self.conn.fetchone()
		return self._filter_result(result)

	def get_one_row(self):
		result = self.conn.fetchone()
		row = self._filter_result(result)
		if row is None:
			raise MigrationError(
				u"Internal error, or H-Sphere CP database inconsistency: "
				u"the query \"%s\" shall return some data, but returned nothing" % self.sql
			)
		return row
	
	def fetchvalue(self):
		result = self.conn.fetchone()
		result = self._filter_result(result)
		if result is not None and len(result) > 0:
			return result[0]
		else:
			return None
	
	def rowcount(self):
		result = self.conn.rowcount
		logger.debug(u"Postgresql rowcount result: '%s'." % result)
		return result
	
	def close(self):
		self.conn.close()

	@staticmethod
	def _filter_result(raw_result, empty_result=None):
		new_result = []
		if raw_result is not None:
			for item in xrange(len(raw_result)):
				if type(raw_result[item]) is str:
					new_result.append(raw_result[item].decode('utf-8'))
				else:
					new_result.append(raw_result[item])
		if new_result is not None:
			logger.debug(u"Postgresql query result: '%s'." % ' '.join(map(unicode, new_result[-5:])))
		return new_result if len(new_result) != 0 else empty_result
