from Acquisition import aq_inner
from collections import defaultdict
from plone.app.registry.browser import controlpanel
from plone.base import PloneMessageFactory as _
from plone.base.interfaces import ISecuritySchema
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.controlpanel.utils import migrate_from_email_login
from Products.CMFPlone.controlpanel.utils import migrate_to_email_login
from Products.Five.browser import BrowserView

import logging

logger = logging.getLogger("Products.CMFPlone")


class SecurityControlPanelForm(controlpanel.RegistryEditForm):
    id = "SecurityControlPanel"
    label = _("Security Settings")
    schema = ISecuritySchema
    schema_prefix = "plone"


class SecurityControlPanel(controlpanel.ControlPanelFormWrapper):
    form = SecurityControlPanelForm


class EmailLogin(BrowserView):
    """View to help in migrating to or from using email as login.

    We used to change the login name of existing users here, but that
    is now done by checking or unchecking the option in the security
    control panel.  Here you can only search for duplicates.
    """

    duplicates = []

    def __call__(self):
        if self.request.form.get("check_email"):
            self.duplicates = self.check_email()
        elif self.request.form.get("check_userid"):
            self.duplicates = self.check_userid()
        return self.index()

    @property
    def _email_list(self):
        context = aq_inner(self.context)
        pas = getToolByName(context, "acl_users")
        emails = defaultdict(list)
        orig_transform = pas.login_transform
        try:
            if not orig_transform:
                # Temporarily set this to lower, as that will happen
                # when turning emaillogin on.
                pas.login_transform = "lower"
            for user in pas.getUsers():
                if user is None:
                    # Created in the ZMI?
                    continue
                email = user.getProperty("email", "")
                if email:
                    email = pas.applyTransform(email)
                else:
                    logger.warning("User %s has no email address.", user.getUserId())
                    # Add the normal login name anyway.
                    email = pas.applyTransform(user.getUserName())
                emails[email].append(user.getUserId())
        finally:
            pas.login_transform = orig_transform
            return emails

    def check_email(self):
        duplicates = []
        for email, userids in self._email_list.items():
            if len(userids) > 1:
                logger.warning(
                    "Duplicate accounts for email address %s: %r", email, userids
                )
                duplicates.append((email, userids))

        return duplicates

    @property
    def _userid_list(self):
        # user ids are unique, but their lowercase version might not
        # be unique.
        context = aq_inner(self.context)
        pas = getToolByName(context, "acl_users")
        userids = defaultdict(list)
        orig_transform = pas.login_transform
        try:
            if not orig_transform:
                # Temporarily set this to lower, as that will happen
                # when turning emaillogin on.
                pas.login_transform = "lower"
            for user in pas.getUsers():
                if user is None:
                    continue
                login_name = pas.applyTransform(user.getUserName())
                userids[login_name].append(user.getUserId())
        finally:
            pas.login_transform = orig_transform
            return userids

    def check_userid(self):
        duplicates = []
        for login_name, userids in self._userid_list.items():
            if len(userids) > 1:
                logger.warning(
                    "Duplicate accounts for lower case user id %s: %r",
                    login_name,
                    userids,
                )
                duplicates.append((login_name, userids))

        return duplicates

    def switch_to_email(self):
        # This is not used and is only here for backwards
        # compatibility.  It avoids a test failure in
        # Products.CMFPlone.
        # XXX: check if this can be removed
        migrate_to_email_login(self.context)

    def switch_to_userid(self):
        # This is not used and is only here for backwards
        # compatibility.  It avoids a test failure in
        # Products.CMFPlone.
        # XXX: check if this can be removed
        migrate_from_email_login(self.context)
