import itertools
import ftplib
import time

from parallels.core.logging import get_logger
from parallels.core.utils.common import safe_string_repr
from parallels.hosting_check import DomainIssue
from parallels.hosting_check import Severity, FTPAuthIssueType
from parallels.hosting_check.messages import MSG

logger = get_logger(__name__)


class FTPAuthChecker(object):
    def check(self, domains_to_check):
        issues = []
        for domain_to_check in domains_to_check:
            self._check_single_domain(domain_to_check, issues)
        return issues

    def _check_single_domain(self, domain, issues):
        if not self._check_web_ip_present(domain, issues):
            return 

        for user in itertools.chain(domain.users, domain.inactive_users):
            if not self._can_verify_user(user):
                issues.append(
                    DomainIssue(
                        domain_name=domain.domain_name, 
                        severity=Severity.WARNING, 
                        category=FTPAuthIssueType.ENCRYPTED_PASSWORD,
                        problem=MSG(
                            FTPAuthIssueType.ENCRYPTED_PASSWORD,
                            server_ip=domain.web_server_ip, 
                            login=user.login)))
            elif self._user_is_active(domain, user):
                self._check_active_user(domain, user, issues)
            else:
                self._check_inactive_user(domain, user, issues)

    @staticmethod
    def _check_web_ip_present(domain_to_check, issues):
        if domain_to_check.web_server_ip is None:
            issues.append(
                DomainIssue(
                    domain_name=domain_to_check.domain_name,
                    severity=Severity.WARNING, 
                    category=FTPAuthIssueType.WEB_IP_NOT_DEFINED,
                    problem=MSG(FTPAuthIssueType.WEB_IP_NOT_DEFINED)
                )
            )
            return False
        return True

    @staticmethod
    def _can_verify_user(user):
        return user.password_type == 'plain'

    @staticmethod
    def _user_is_active(domain, user):
        return not domain.inactive_users or user not in domain.inactive_users

    def _check_active_user(self, domain_to_check, user, issues):
        try:
            auth_successful, message = self._check_ftp_auth(
                domain_to_check.web_server_ip, user
            )
            if not auth_successful:
                issues.append(
                    DomainIssue(
                        domain_name=domain_to_check.domain_name,
                        severity=Severity.ERROR, 
                        category=FTPAuthIssueType.CHECK_FAILED,
                        problem=MSG(
                            FTPAuthIssueType.CHECK_FAILED,
                            server_ip=domain_to_check.web_server_ip, 
                            login=user.login, 
                            reason=message
                        )
                    )
                )
        except KeyboardInterrupt:
            # for compatibility with python 2.4
            raise
        except Exception as e:
            logger.debug(u"Exception:", exc_info=e)
            issues.append(
                DomainIssue(
                    domain_name=domain_to_check.domain_name,
                    severity=Severity.WARNING, 
                    category=FTPAuthIssueType.INTERNAL_ERROR,
                    problem=MSG(
                        FTPAuthIssueType.INTERNAL_ERROR,
                        server_ip=domain_to_check.web_server_ip,
                        login=user.login,
                        reason=safe_string_repr(e)
                    )
                )
            )

    def _check_inactive_user(self, domain_to_check, user, issues):
        try:
            auth_successful, _ = self._check_ftp_auth(
                domain_to_check.web_server_ip, user
            )
            if auth_successful:
                issues.append(
                    DomainIssue(
                        domain_name=domain_to_check.domain_name,
                        severity=Severity.ERROR, 
                        category=FTPAuthIssueType.INACTIVE_USER_CAN_CONNECT,
                        problem=MSG(
                            FTPAuthIssueType.INACTIVE_USER_CAN_CONNECT,
                            server_ip=domain_to_check.web_server_ip,
                            login=user.login
                        )
                    )
                )
        except KeyboardInterrupt:
            # for compatibility with python 2.4
            raise
        except Exception as e:
            logger.debug(u"Exception:", exc_info=e)
            issues.append(
                DomainIssue(
                    domain_name=domain_to_check.domain_name,
                    severity=Severity.WARNING, 
                    category=FTPAuthIssueType.INTERNAL_ERROR,
                    problem=MSG(
                        FTPAuthIssueType.INTERNAL_ERROR,
                        server_ip=domain_to_check.web_server_ip,
                        login=user.login,
                        reason=safe_string_repr(e)
                    )
                )
            )

    def _check_ftp_auth(self, web_server_ip, user):
        try:
            logger.debug(MSG(
                'ftp_log_checking', 
                user=user.login, server_ip=web_server_ip
            ))
            self._check_ftp_login(web_server_ip, user.login, user.password)
            return True, None
        except ftplib.all_errors as e:
            logger.exception()
            return False, safe_string_repr(e)

    @staticmethod
    def _check_ftp_login(host, login, password):
        ftp = ftplib.FTP(host)
        try:
            ftp.login(login, password)
            ftp.dir(lambda x: None)  # just execute a command
            # we sleep 250ms due to ftp server of PPA/Plesk works via xinetd.d
            # that has default limit for 50 connections in 10 seconds interval
            time.sleep(0.25)
        finally:
            ftp.quit()
