import logging

import imaplib
import smtplib
import poplib
import socket

from parallels.hosting_check import DomainIssue
from parallels.hosting_check import Severity, MailAuthIssueType

from parallels.hosting_check.messages import MSG

logger = logging.getLogger(__name__)


class MailAuthChecker(object):
	def __init__(self, messages_delta_limit=5):
		self.messages_delta_limit = messages_delta_limit

	def check(self, domains_to_check):
		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):
		issues = []

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

		if domain_to_check.mail_server_ip is None:
			add_issue(
				severity=Severity.WARNING, 
				category=MailAuthIssueType.MAIL_IP_NOT_DEFINED,
				problem=MSG(MailAuthIssueType.MAIL_IP_NOT_DEFINED)
			)
			return issues 

		exist_errors = []
		imap_errors = []
		imap_internal_errors = []
		smtp_errors = []
		smtp_internal_errors = []
		pop3_errors = []
		pop3_internal_errors = []
		messages_count_pop3_errors = []
		messages_count_internal_errors = []
		messages_count_significant_difference = []
		messages_count_unsignificant_difference = []

		for user in domain_to_check.users:
			base_login = user.login.split("@")[0]
			if (
				domain_to_check.target_panel_mail_users is not None 
				and
				base_login not in domain_to_check.target_panel_mail_users
			):
				exist_errors.append(user.login)
				continue

			try:
				self._check_imap_login(
					domain_to_check.mail_server_ip, user.login, user.password
				)
			except MailServerConnectionError, e:
				logger.debug(u"Exception:", exc_info=e)
				imap_errors.append((user.login, unicode(e),))
			except KeyboardInterrupt:
				# for compatibility with python 2.4
				raise
			except Exception, e:
				logger.debug(u"Exception:", exc_info=e)
				imap_internal_errors.append((user.login, unicode(e),))

			try:
				self._check_smtp_login(
					domain_to_check.mail_server_ip, user.login, user.password
				)
			except MailServerConnectionError, e:
				logger.debug(u"Exception:", exc_info=e)
				smtp_errors.append((user.login, unicode(e),))
			except KeyboardInterrupt:
				# for compatibility with python 2.4
				raise
			except Exception, e:
				logger.debug(u"Exception:", exc_info=e)
				smtp_internal_errors.append((user.login, unicode(e),))

			try:
				self._check_pop3_login(
					domain_to_check.mail_server_ip, user.login, user.password
				)
				if (
					domain_to_check.source_users is not None 
					and 
					domain_to_check.source_mail_server_ip is not None
				):
					self._check_count_mail_messages(
						domain_to_check, user, 
						messages_count_significant_difference,
						messages_count_unsignificant_difference,
						messages_count_pop3_errors,
						messages_count_internal_errors,
					)
			except MailServerConnectionError, e:
				logger.debug(u"Exception:", exc_info=e)
				pop3_errors.append((user.login, unicode(e),))
			except KeyboardInterrupt:
				# for compatibility with python 2.4
				raise
			except Exception, e:
				logger.debug(u"Exception:", exc_info=e)
				pop3_internal_errors.append((user.login, unicode(e),))

		if len(exist_errors) > 0:
			add_issue(
				severity=Severity.ERROR,
				category=MailAuthIssueType.MAILUSER_NOT_EXIST,
				problem=MSG(
					MailAuthIssueType.MAILUSER_NOT_EXIST,
					mailusers_list=u", ".join(
						'%s' % mailbox_fullname 
						for mailbox_fullname in exist_errors
					)
				)
			)

		if len(imap_errors) > 0:
			add_issue(
				severity=Severity.ERROR,
				category=MailAuthIssueType.CHECK_FAILED_IMAP,
				problem=self._format_error_list(
					MailAuthIssueType.CHECK_FAILED_IMAP, 
					imap_errors
				)
			)

		if len(imap_internal_errors) > 0:
			add_issue(
				severity=Severity.WARNING,
				category=MailAuthIssueType.INTERNAL_ERROR_IMAP,
				problem=self._format_error_list(
					MailAuthIssueType.INTERNAL_ERROR_IMAP,
					imap_internal_errors
				)
			)

		if len(smtp_errors) > 0:
			add_issue(
				severity=Severity.ERROR,
				category=MailAuthIssueType.CHECK_FAILED_SMTP,
				problem=self._format_error_list(
					MailAuthIssueType.CHECK_FAILED_SMTP, 
					smtp_errors
				)
			)

		if len(smtp_internal_errors) > 0:
			add_issue(
				severity=Severity.WARNING,
				category=MailAuthIssueType.INTERNAL_ERROR_SMTP,
				problem=self._format_error_list(
					MailAuthIssueType.INTERNAL_ERROR_SMTP,
					smtp_internal_errors
				)
			)

		if len(pop3_errors) > 0:
			add_issue(
				severity=Severity.ERROR, 
				category=MailAuthIssueType.CHECK_FAILED_POP3,
				problem=self._format_error_list(
					MailAuthIssueType.CHECK_FAILED_POP3,
					pop3_errors
				)
			)

		if len(pop3_internal_errors) > 0:
			add_issue(
				severity=Severity.WARNING, 
				category=MailAuthIssueType.INTERNAL_ERROR_POP3,
				problem=self._format_error_list(
					MailAuthIssueType.INTERNAL_ERROR_POP3,
					pop3_internal_errors
				)
			)

		if len(messages_count_pop3_errors) > 0:
			add_issue(
				severity=Severity.ERROR,
				category=MailAuthIssueType.MESSAGES_COUNT_POP3_ERROR,
				problem=self._format_error_list(
					MailAuthIssueType.MESSAGES_COUNT_POP3_ERROR,
					messages_count_pop3_errors
				)
			)

		if len(messages_count_internal_errors) > 0:
			add_issue(
				severity=Severity.WARNING,
				category=MailAuthIssueType.MESSAGES_COUNT_INTERNAL_ERROR,
				problem=self._format_error_list(
					MailAuthIssueType.MESSAGES_COUNT_INTERNAL_ERROR,
					messages_count_internal_errors
				)
			)

		if len(messages_count_significant_difference) > 0:
			add_issue(
				severity=Severity.ERROR,
				category=MailAuthIssueType.MESSAGES_COUNT_SIGNIFICANT_DIFFERENCE,
				problem=self._format_error_list(
					MailAuthIssueType.MESSAGES_COUNT_SIGNIFICANT_DIFFERENCE,
					messages_count_significant_difference
				)
			)

		if len(messages_count_unsignificant_difference) > 0:
			add_issue(
				severity=Severity.WARNING,
				category=MailAuthIssueType.MESSAGES_COUNT_INSIGNIFICANT_DIFFERENCE,
				problem=self._format_error_list(
					MailAuthIssueType.MESSAGES_COUNT_INSIGNIFICANT_DIFFERENCE,
					messages_count_unsignificant_difference
				)
			)

		return issues

	@staticmethod
	def _format_error_list(error_message, error_list):
		return MSG(
			error_message,
			mailusers_list=u"\n".join([
				"- %s: %s" % (mailbox_fullname, error_msg)
				for mailbox_fullname, error_msg in error_list
			])
		)

	def _check_imap_login(self, host, login, password):
		imap = imaplib.IMAP4(host)
		try:
			imap.login(self._encode_idn_mailname(login), password.encode('utf-8'))
			# execute some command after login
			imap.list()
		except (imaplib.IMAP4.error, socket.error), e:
			logger.debug("Exception: ", exc_info=True)
			raise MailServerConnectionError(
				MSG('failed_to_login_imap', host=host, login=login, reason=unicode(e))
			)
		finally:
			imap.logout()

	def _check_smtp_login(self, host, login, password):
		smtp = smtplib.SMTP(host)
		try:
			# just login and quit - that's enough
			smtp.login(self._encode_idn_mailname(login), password.encode('utf-8'))
		except (smtplib.SMTPException, socket.error), e:
			logger.debug("Exception: ", exc_info=True)
			raise MailServerConnectionError(
				MSG('failed_to_login_smtp', host=host, login=login, reason=unicode(e))
			)
		finally:
			smtp.quit()

	def _check_pop3_login(self, host, login, password):
		pop3 = poplib.POP3(host)
		try:
			# just login and quit - that's enough
			pop3.user(self._encode_idn_mailname(login))
			pop3.pass_(password.encode('utf-8'))
		except (poplib.error_proto, socket.error), e:
			logger.debug("Exception: ", exc_info=True)
			raise MailServerConnectionError(
				MSG('failed_to_login_pop3', host=host, login=login, reason=unicode(e))
			)
		finally:		
			pop3.quit()

	def _get_count_mail_messages(self, host, login, password):
		# POP3 is always enabled, IMAP isn't enabled always.
		# For example, it disabled for PfW 8.x
		pop3 = poplib.POP3(host)
		try:
			pop3.user(self._encode_idn_mailname(login))
			pop3.pass_(password)
			count = pop3.stat()[0]
			return int(count)
		except (poplib.error_proto, socket.error), e:
			logger.debug("Exception: ", exc_info=True)
			raise MailServerConnectionError("POP3 connection to '%s' as '%s' failed: %s" % (host, login, e))
		finally:		
			pop3.quit()

	def _check_count_mail_messages(
		self, domain_to_check, user,
		messages_count_significant_difference, 
		messages_count_unsignificant_difference,
		messages_count_errors, messages_count_internal_errors
	):
		try:
			source_count = self._get_count_mail_messages(
				domain_to_check.source_mail_server_ip,
				user.login,
				self._get_password_by_login(
					domain_to_check.source_users, user.login
				)
			)
			target_count = self._get_count_mail_messages(
				domain_to_check.mail_server_ip,
				user.login,
				user.password
			)
			if source_count > target_count + self.messages_delta_limit:
				e = MSG(
					'mail_auth_different_messages_count',
					source_count=source_count, target_count=target_count
				)
				messages_count_significant_difference.append((user.login, e,))
			elif source_count > target_count:
				e = MSG(
					'mail_auth_different_messages_count',
					source_count=source_count, target_count=target_count
				)
				messages_count_unsignificant_difference.append((user.login, e,))
		except MailServerConnectionError, e:
			logger.debug(u"Exception:", exc_info=e)
			messages_count_errors.append((user.login, unicode(e),))
		except KeyboardInterrupt:
			# for compatibility with python 2.4
			raise
		except Exception, e:
			logger.debug(u"Exception:", exc_info=e)
			messages_count_internal_errors.append((user.login, unicode(e),))

	@staticmethod
	def _get_password_by_login(source_users, login):
		for source_user in source_users:
			if source_user.login == login:
				return source_user.password
		raise Exception(MSG(
			'mail_auth_cant_find_mailuser_on_source', 
			login=login
		))

	@staticmethod
	def _encode_idn_mailname(mailname):
		parts = mailname.split('@', 1)
		if len(parts) == 1:
			return parts
		else:
			return u'%s@%s' % (parts[0], parts[1].encode('idna'))

class MailServerConnectionError(Exception):
	pass