from parallels.core.utils import windows_utils

# Hosting checks source
from parallels.core.hosting_check.entity_source.common import \
	HostingObjectSubscriptionBase
from parallels.core.hosting_check import \
	BaseHostingObject, ChildHostingObjectsList, HostingCheckEntitiesList
from parallels.core.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.core.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.core.dump.dump.PleskBackupSource*
		- subscription - subscription from backup (parallels.core.dump.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.core.dump.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_info in subscription_databases:
			db_type = db_info.source_database_server.type()
			if db_type == 'mysql':
				converted_db = self._get_mysql_database_object(db_info)
				if converted_db is not None:
					mysql_databases_entity.databases.append(converted_db)
			elif db_type == 'mssql':
				mssql_databases_entity.databases.append(
					self._get_mssql_database_object(db_info)
				)
			elif db_type == 'postgresql':
				pgsql_databases_entity.databases.append(
					self._get_pgsql_database_object(db_info)
				)

		return result

	def _get_mysql_database_object(self, database_info):
		"""Initialize and return MySQLDatabase object"""

		source = database_info.source_database_server
		target = database_info.target_database_server

		if source.is_windows() == target.is_windows():

			source_mysql_client_cli = self.database_info_source.get_source_mysql_client_cli(database_info)

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

			users = self.database_info_source.get_db_users(
				self.subscription.name,
				source.panel_server.node_id,
				database_info.database_name,
				source.type()
			) 

			return MySQLDatabase(
				source_access=MySQLDatabaseAccess(
					mysql_client_cli=source_mysql_client_cli,
					host=source.host(),
					port=source.port(),
					admin_user=User(
						source.user(),
						source.password()
					),
				),
				target_access=MySQLDatabaseAccess(
					mysql_client_cli=target_mysql_client_cli,
					host=target.host(),
					port=target.port(),
					admin_user=User(
						target.user(),
						target.password()
					),
				),
				name=database_info.database_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, database_info):
		"""Initialize and return MSSQLDatabase object"""

		source = database_info.source_database_server
		target = database_info.target_database_server

		def get_session_file_path(filename, target_node=target):
			return target_node.get_session_file_path(filename)

		users = self.database_info_source.get_db_users(
			self.subscription.name,
			source.panel_server.node_id,
			database_info.database_name,
			source.type()
		) 
		
		return MSSQLDatabase(
			name=database_info.database_name,
			source_access=MSSQLDatabaseSourceAccess(
				host=windows_utils.get_dbbackup_mssql_host(source.host(), source.ip()),
				admin_user=User(source.user(), source.password()),
			),
			target_access=MSSQLDatabaseTargetAccess(
				host=target.host(),
				admin_user=User(target.user(), target.password()),
			),
			script_runner=MSSQLDatabaseScriptRunner(
				runner=HostingCheckerRunnerAdapter(target.runner),
				get_session_file_path=get_session_file_path,
			),
			users=users, 
		)

	def _get_pgsql_database_object(self, database_info):
		"""Initialize and return PostgreSQLDatabase object"""

		source = database_info.source_database_server
		target = database_info.target_database_server

		users = self.database_info_source.get_db_users(
			self.subscription.name,
			source.panel_server.node_id,
			database_info.database_name,
			source.type()
		)

		return PostgreSQLDatabase(
			name=database_info.database_name,
			source_access=PostgreSQLDatabaseAccess(
				runner=HostingCheckerRunnerAdapter(source.runner),
				host=source.host(),
				port=source.port(),
				admin_user=User(source.user(), source.password()),
			),
			target_access=PostgreSQLDatabaseAccess(
				runner=HostingCheckerRunnerAdapter(target.runner),
				host=target.host(),
				port=target.port(),
				admin_user=User(target.user(), target.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(self, database_info):
		"""Get MySQL client CLI on source server for specified database
		
		Return instance of parallels.hosting_check.MySQLClientCLI
		"""
		raise NotImplementedError()

	def list_target_panel_databases(self, 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()
