from parallels.common.utils import windows_utils
from parallels.common.utils import migrator_utils

# Hosting checks source
from parallels.common.hosting_check.entity_source.common import \
	HostingObjectSubscriptionBase
from parallels.common.hosting_check import \
	BaseHostingObject, ChildHostingObjectsList, HostingCheckEntitiesList
from parallels.common.hosting_check.utils.runner_adapter import \
	HostingCheckerRunnerAdapter

# MySQL types
from parallels.hosting_check import DomainMySQLDatabases
from parallels.hosting_check import MySQLDatabase, MySQLDatabaseAccess
from parallels.hosting_check.checkers.mysql_database_checker import \
		UnixMySQLClientCLI, WindowsMySQLClientCLI

# MSSQL types
from parallels.hosting_check import DomainMSSQLDatabases
from parallels.hosting_check import MSSQLDatabase, \
	MSSQLDatabaseSourceAccess, MSSQLDatabaseTargetAccess, \
	MSSQLDatabaseScriptRunner

# PostgreSQL types
from parallels.hosting_check import DomainPostgreSQLDatabases
from parallels.hosting_check import PostgreSQLDatabase
from parallels.hosting_check import PostgreSQLDatabaseAccess

# Common types
from parallels.common.hosting_check import User, TargetPanelDatabase

class HostingObjectDatabaseSubscription(HostingObjectSubscriptionBase):
	"""Source for hosting checks - subscription with database hosting"""
	def __init__(self, backup, subscription, database_info_source):
		"""
		Arguments:
		- database_info_source - instance of DatabasesInfoSourceInterface
		- backup - source backup instance
		  (parallels.common.plesk_backup.plesk_backup_xml.PleskBackupSource*
		- subscription - subscription from backup 
		  (parallels.common.plesk_backup.model.Subscription*)
		"""
		super(HostingObjectDatabaseSubscription, self).__init__(
			backup, subscription
		)
		self.database_info_source = database_info_source

	def get_child_hosting_objects(self):
		"""Get child hosting objects (database services) to check 
		
		Return: ChildHostingObjectsList which contains list of
		objects-subclasses of BaseHostingObject class
		"""
		return ChildHostingObjectsList(
			# the only child hosting object is database server
			child_hosting_objects=[HostingObjectDatabaseService(
				self.subscription, self.database_info_source
			)]
		)

class HostingObjectDatabaseService(BaseHostingObject):
	"""Source for hosting checks - database service
	
	Domain may be represented by main domain of subscription,
	addon domain or subdomain
	"""
	def __init__(self, subscription, database_info_source):
		"""Arguments:
		- subscription - subscription from backup 
		  (parallels.common.plesk_backup.model.Subscription*)
		- database_info_source - instance of DatabasesInfoSourceInterface
		"""
		self.subscription = subscription
		self.name = subscription.name
		self.type = 'Database service'
		self.database_info_source = database_info_source

	def get_hosting_check_entities(self):
		"""Return a list of databases to be checked.

		Returns: an object of type 'HostingCheckEntitiesList'
		"""
		result = HostingCheckEntitiesList()

		# initialize entities to put information about databases to check
		mysql_databases_entity = DomainMySQLDatabases(
			domain_name=self.subscription.name,
			target_panel_databases=[],
			databases=[]
		)
		result.hosting_check_entities.append(mysql_databases_entity)
		mssql_databases_entity = DomainMSSQLDatabases(
			domain_name=self.subscription.name,
			target_panel_databases=[],
			databases=[]
		)
		result.hosting_check_entities.append(mssql_databases_entity)
		pgsql_databases_entity = DomainPostgreSQLDatabases(
			domain_name=self.subscription.name,
			target_panel_databases=[],
			databases=[]
		)
		result.hosting_check_entities.append(pgsql_databases_entity)

		# fill information about databases that exist on target panel
		for entity, db_type in (
			(mysql_databases_entity, 'mysql'),
			(mssql_databases_entity, 'mssql'),
			(pgsql_databases_entity, 'postgresql')
		):
			target_databases = \
				self.database_info_source.list_target_panel_databases(
					self.subscription.name, db_type 
				)
			for name, users in target_databases:
				entity.target_panel_databases.append(
					TargetPanelDatabase(
						name=name,
						users=users
					)
				)

		# fill information about databases that are expected to be restored
		subscription_databases = \
			self.database_info_source.list_databases_to_copy().get(
				self.subscription.name, []
			)
		for db in subscription_databases:
			if db.src_db_server.dbtype == 'mysql':
				converted_db = self._get_mysql_database_object(db)
				if converted_db is not None:
					mysql_databases_entity.databases.append(converted_db)
			elif db.src_db_server.dbtype == 'mssql':
				mssql_databases_entity.databases.append(
					self._get_mssql_database_object(db)
				)
			elif db.src_db_server.dbtype == 'postgresql':
				pgsql_databases_entity.databases.append(
					self._get_pgsql_database_object(db)
				)

		return result

	def _get_mysql_database_object(self, db):
		"""Initialize and return MySQLDatabase object"""
		is_windows_target_node = db.target_node.is_windows()
		is_windows_source_node = db.src_server_access.is_windows

		src_server_info = migrator_utils.DbServerInfo.from_plesk_backup(
			db.src_db_server
		)
		dst_server_info = migrator_utils.DbServerInfo.from_plesk_api(
			db.dst_db_server
		)

		if is_windows_source_node == is_windows_target_node:

			source_mysql_client_cli = \
				self.database_info_source.get_source_mysql_client_cli(db)

			if not db.target_node.is_windows():
				target_mysql_client_cli = UnixMySQLClientCLI(
					HostingCheckerRunnerAdapter(db.target_node.runner)
				)
			else:
				target_mysql_client_cli = WindowsMySQLClientCLI(
					HostingCheckerRunnerAdapter(db.target_node.runner), 
					db.target_node.mysql_bin
				)

			users = self.database_info_source.get_db_users(
				self.subscription.name, db.plesk_id, 
				db.db_name, db.src_db_server.dbtype
			) 

			return MySQLDatabase(
				source_access=MySQLDatabaseAccess(
					mysql_client_cli=source_mysql_client_cli,
					host=src_server_info.host,
					port=src_server_info.port,
					admin_user=User(
						src_server_info.login, 
						src_server_info.password
					),
				),
				target_access=MySQLDatabaseAccess(
					mysql_client_cli=target_mysql_client_cli,
					host=dst_server_info.host,
					port=dst_server_info.port,
					admin_user=User(
						db.target_node.login(), 
						db.target_node.password()
					),
				),
				name=db.db_name,
				users=users, 
			)
		else:
			# we do not copy MySQL databases from Windows to Linux and vice
			# versa
			return None

	def _get_mssql_database_object(self, db):
		"""Initialize and return MSSQLDatabase object"""
		def get_session_file_path(filename, target_node=db.target_node):
			return target_node.get_session_file_path(filename)

		src_server_info = migrator_utils.DbServerInfo.from_plesk_backup(
			db.src_db_server
		)
		dst_server_info = migrator_utils.DbServerInfo.from_plesk_api(
			db.dst_db_server
		)

		users = self.database_info_source.get_db_users(
			self.subscription.name, db.plesk_id, db.db_name, 
			db.src_db_server.dbtype
		) 
		
		return MSSQLDatabase(
			name=db.db_name,
			source_access=MSSQLDatabaseSourceAccess(
				host=windows_utils.get_dbbackup_mssql_host(
					src_server_info.host, db.src_server_access.ip
				),
				admin_user=User(
					src_server_info.login, 
					src_server_info.password
				),
			),
			target_access=MSSQLDatabaseTargetAccess(
				host=dst_server_info.host,
				admin_user=User(
					db.target_node.login(), 
					db.target_node.password()
				),
			),
			script_runner=MSSQLDatabaseScriptRunner(
				runner=HostingCheckerRunnerAdapter(db.target_node.runner),
				get_session_file_path=get_session_file_path,
			),
			users=users, 
		)

	def _get_pgsql_database_object(self, db):
		"""Initialize and return PostgreSQLDatabase object"""
		users = self.database_info_source.get_db_users(
			self.subscription.name, db.plesk_id, db.db_name, 
			db.src_db_server.dbtype
		) 

		src_server_info = migrator_utils.DbServerInfo.from_plesk_backup(
			db.src_db_server
		)
		dst_server_info = migrator_utils.DbServerInfo.from_plesk_api(
			db.dst_db_server
		)
		
		source_runner = lambda: self.database_info_source.get_source_pgsql_runner(db) 
		return PostgreSQLDatabase(
			name=db.db_name,
			source_access=PostgreSQLDatabaseAccess(
				runner=HostingCheckerRunnerAdapter(source_runner),
				host=src_server_info.host,
				port=src_server_info.port,
				admin_user=User(
					src_server_info.login, 
					src_server_info.password
				),
			),
			target_access=PostgreSQLDatabaseAccess(
				runner=HostingCheckerRunnerAdapter(db.target_node.runner),
				host=dst_server_info.host,
				port=dst_server_info.port,
				admin_user=User(
					db.target_node.login(), 
					db.target_node.password()
				),
			),
			users=users, 
		)

class DatabasesInfoSourceInterface(object):
	"""Source of information about databases"""

	def list_databases_to_copy(self):
		"""List databases that are considered to be transfered by the tool
		
		Should return dictionary - key is subscription name, value is a list
		of databases, in the same format as returned by 
		Migrator._list_databases_to_copy
		"""
		raise NotImplementedError()

	def get_source_mysql_client_cli(db):
		"""Get MySQL client CLI on source server for specified database
		
		Return instance of parallels.hosting_check.MySQLClientCLI
		"""
		raise NotImplementedError()

	def get_source_pgsql_runner(db):
		"""Get runner to source server with access to PostgreSQL databases
		
		Return instance of parallels.run_command.BaseRunner
		"""
		raise NotImplementedError()

	def list_target_panel_databases(subscription_name, database_type):
		"""List databases that exist on target panel
		
		Arguments:
		- subscription_name - function will list databases of that subscription
		- database_type - type of database to list ('mysql' or 'mssql')

		Returns list of strings - database names
		"""
		raise NotImplementedError()

	@staticmethod
	def get_db_users(subscription_name, source_id, db_name, db_type):
		"""Get database users of specified subscription"""
		raise NotImplementedError()
