import logging
import os.path

import parallels.hosting_check
from parallels.hosting_check import DomainIssue, Severity
from parallels.hosting_check import MSSQLDatabaseIssueType as IT
from parallels.hosting_check.utils.powershell import Powershell
from parallels.hosting_check.utils.text_utils import format_list

from parallels.hosting_check.messages import MSG

logger = logging.getLogger(__name__)

class MSSQLDatabaseChecker(object):
	def check(self, domains_to_check):
		"""
		Arguments:
		- domains_to_check - list of DomainMSSQLDatabases
		"""
		issues = []
		for domain_to_check in domains_to_check:
			issues += self._check_single_domain(domain_to_check)
		return issues

	def _check_single_domain(self, domain_to_check):
		"""
		Arguments:
		- domain_to_check - DomainMSSQLDatabases
		"""
		issues = []

		def add_issue(severity, category, problem):
			issues.append(
				DomainIssue(
					domain_name=domain_to_check.domain_name,
					severity=severity, 
					category=category, 
					problem=problem
				)
			)

		for database in domain_to_check.databases:
			if not self._database_exists_panel(
				domain_to_check, database, issues
			):
				# skip all the other checks - no sense to check 
				# if it even was not created in the panel
				continue 

			try:
				source_tables = None

				if not _check_login(
					database.script_runner, database.target_access,
					database.target_access.admin_user
				):
					add_issue(
						severity=Severity.ERROR, 
						category=IT.FAILED_TO_LOGIN_TO_TARGET_SERVER_AS_ADMIN,
						problem=MSG(
							IT.FAILED_TO_LOGIN_TO_TARGET_SERVER_AS_ADMIN,
							host=database.target_access.host,
							database=database.name,
						)
					)
					continue

				if not _check_login(
					database.script_runner, database.target_access, 
					database.target_access.admin_user, database.name
				):
					add_issue(
						severity=Severity.ERROR, 
						category=IT.FAILED_TO_LOGIN_TO_TARGET_DATABASE_AS_ADMIN,
						problem=MSG(
							IT.FAILED_TO_LOGIN_TO_TARGET_DATABASE_AS_ADMIN,
							database=database.name,
							host=database.target_access.host,
						)
					)
					continue

				try:
					source_tables = _list_tables(
						database.script_runner, database.source_access, 
						database.name
					)
				except Exception, e:
					logger.debug(u"Exception:", exc_info=e)
					add_issue(
						severity=Severity.WARNING, 
						category=IT.FAILED_TO_FETCH_TABLES_FROM_SOURCE,
						problem=MSG(
							IT.FAILED_TO_FETCH_TABLES_FROM_SOURCE,
							host=database.source_access.host, 
							database=database.name,
						)
					)

				if source_tables is not None:
					target_tables = _list_tables(
						database.script_runner, database.target_access, 
						database.name
					)
					if not (target_tables >= source_tables):
						add_issue(
							severity=Severity.ERROR, 
							category=IT.DIFFERENT_TABLES_SET,
							problem=MSG(
								IT.DIFFERENT_TABLES_SET,
								database=database.name,
								tables_list=format_list(
									source_tables - target_tables
								)
							)
						)

				for user in database.users:
					if not self._database_user_exists_panel(
						domain_to_check, database, user, issues
					):
						# proceed to the next checks only if user exist on the
						# target panel
						continue

					if not _check_login(
						database.script_runner, database.target_access, 
						user, database.name
					):
						add_issue(
							severity=Severity.ERROR, 
							category=IT.FAILED_TO_LOGIN_AS_USER,
							problem=MSG(
								IT.FAILED_TO_LOGIN_AS_USER,
								user=user.login, database=database.name
							)
						)
			except Exception, e:
				logger.debug(u"Exception:", exc_info=e)
				add_issue(
					severity=Severity.WARNING, category=IT.INTERNAL_ERROR,
					problem=MSG(
						IT.INTERNAL_ERROR,
						reason=str(e), database=database.name
					)
				)

		return issues

	@staticmethod
	def _database_exists_panel(domain_to_check, database, issues):
		target_panel_databases = None
		if domain_to_check.target_panel_databases is not None:
			target_panel_databases = set([])
			for db in domain_to_check.target_panel_databases:
				target_panel_databases.add(db.name)

		if (
			target_panel_databases is not None 
			and 
			database.name not in target_panel_databases
		):
			issues.append(
				DomainIssue(
					domain_name=domain_to_check.domain_name,
					severity=Severity.ERROR, 
					category=IT.DATABASE_DOES_NOT_EXIST_IN_PANEL,
					problem=MSG(
						IT.DATABASE_DOES_NOT_EXIST_IN_PANEL,
						database=database.name,
					)
				)
			)
			return False
		else:
			return True

	@staticmethod
	def _database_user_exists_panel(domain_to_check, database, user, issues):
		if domain_to_check.target_panel_databases is not None:
			target_panel_databases = dict()
			for db in domain_to_check.target_panel_databases:
				target_panel_databases[db.name] = db.users
			target_panel_users = target_panel_databases[database.name]
		else:
			target_panel_users = None

		if (
			target_panel_users is not None 
			and 
			user.login not in target_panel_users
		):
			issues.append(
				DomainIssue(
					domain_name=domain_to_check.domain_name,
					severity=Severity.ERROR, 
					category=IT.DATABASE_USER_DOES_NOT_EXIST_IN_PANEL,
					problem=MSG(
						IT.DATABASE_USER_DOES_NOT_EXIST_IN_PANEL,
						user=user.login, database=database.name,
					)
				)
			)
			return False
		else:
			return True

def _list_tables(script_runner, database_access, database_name):
	check_script_name = 'list_mssql_tables.ps1'
	remote_check_script_path = script_runner.get_session_file_path(
		check_script_name
	)
	try:
		script_runner.runner.connect()
		script_contents = read_file(_get_script_local_path(check_script_name))
		script_runner.runner.upload_file_content(
			remote_check_script_path, script_contents
		)
		powershell = Powershell(script_runner.runner, input_format_none=True)
		exit_code, stdout, _ = powershell.execute_script_unchecked(
			remote_check_script_path, {
				'hostname': database_access.host, 
				'login': database_access.admin_user.login, 
				'password': database_access.admin_user.password,
				'database': database_name,
			}
		)

		if exit_code != 0:
			raise Exception(MSG('mssql_database_listing_script_failed'))

		return (
			set([d.strip() for d in stdout.split() if d.strip() != '']) -
			# Exclude these table as they are marked as user ones by MSSQL, but actually system ones
			# (used by Diagram Designer of MS SQL Server Management Studio).
			# They are not copied by default, and we don't want to check them.
			set(['sysdiagrams', 'dtproperties'])
		)
	finally:
		script_runner.runner.disconnect()

def _check_login(script_runner, database_access, user, database_name=None):
	check_script_name = 'check_mssql_login.ps1'
	remote_check_script_path = script_runner.get_session_file_path(
		check_script_name
	)
	try:
		script_runner.runner.connect()
		script_contents = read_file(_get_script_local_path(check_script_name))
		script_runner.runner.upload_file_content(
			remote_check_script_path, script_contents
		)
		args = {
			'hostname': database_access.host, 
			'login': user.login, 
			'password': user.password
		}
		if database_name is not None:
			args['database'] = database_name

		powershell = Powershell(script_runner.runner, input_format_none=True)
		_, stdout, _ = powershell.execute_script_unchecked(
			remote_check_script_path, args
		)
		return stdout.strip() == 'OK'
	finally:
		script_runner.runner.disconnect()

def read_file(filename):
	f = open(filename)
	try:
		return f.read()
	finally:
		f.close()

def _get_script_local_path(filename):
	return os.path.join(_get_module_root_path(), 'scripts', filename)

def _get_module_root_path():
	# Get path to package root directory.
	dirs = [p for p in parallels.hosting_check.__path__]
	return dirs[0]
