from plone import api
from ploneintranet import api as pi_api
from ploneintranet.layout.interfaces import INoBarcelonetaLayer
from ploneintranet.userprofile.interfaces import IPloneintranetUserprofileLayer
from Products.CMFPlacefulWorkflow.PlacefulWorkflowTool import WorkflowPolicyConfig_id
from Products.membrane.interfaces import IMembraneGroupGroups
from zope.event import notify
from zope.globalrequest import getRequest
from zope.lifecycleevent import ObjectModifiedEvent

import logging

logger = logging.getLogger(__name__)


def clear_workspace_groups_plugin_cache(obj, event):
    adapter = IMembraneGroupGroups(obj, None)
    if not adapter:
        return
    adapter.clear_cache()


def clear_acl_users_cache(obj, event):
    do_clear = False
    if isinstance(event, ObjectModifiedEvent):
        for description in event.descriptions:
            if "members" in getattr(description, "attributes", {}):
                do_clear = True
    elif obj.members:
        do_clear = True
    if do_clear:
        acl_users = api.portal.get_tool("acl_users")
        if acl_users.ZCacheable_enabled():
            acl_users.ZCacheable_invalidate()


def on_user_login(event):
    """Automatically create a content user if one doesn't exist.

    This supports the scenario where authentication is provided by
    an external source (e.g. LDAP), but we still need a local
    membrane profile to store PI-specific data.
    """
    request = getRequest()
    if not IPloneintranetUserprofileLayer.providedBy(request):
        return
    if not INoBarcelonetaLayer.providedBy(request):
        return

    portal = api.portal.get()
    member = event.principal
    # Workaround for autologin after password reset: member is anon, userid is None
    userid = member.getId() or portal.REQUEST.form.get("userid")

    profiles = portal.profiles
    properties = {}

    # Active Directory is case insensitive, but our user ids are very much case
    # sensitive. Given a userid "guidot" you can log in as "GUIDOT" and v.v.
    # Make sure that the Plone userid we are using case-matches the actual user
    # id as returned from AD. This is especially salient because even when
    # use_email_as_username is configured, you can still log in with our userid
    with api.env.adopt_roles(roles=("Manager",)):
        sync_view = api.content.get_view("sync-users", profiles, portal.REQUEST)
        if sync_view.external_source:
            # catch edge case where users==None
            external_users = getattr(sync_view.external_source, "users", {}) or {}
            if userid in external_users:
                properties["source"] = sync_view.external_source_id
                try:
                    # external_users.get("GUIDOT").attributes.get("id") -> "guidot"
                    userid = external_users.get(userid).attributes.get("id", userid)
                except AttributeError:
                    pass

    # With the userid resolved to its proper case, we can check if it has a profile
    membrane_user = pi_api.userprofile.get(userid)
    if membrane_user is not None:
        lang = membrane_user.language or membrane_user.preferred_content_language
        if lang:
            lt = api.portal.get_tool("portal_languages")
            lt.setLanguageCookie(lang, request=request)
        return

    # No profile: create one
    with api.env.adopt_roles(roles=("Manager",)):
        logger.info(f"Auto-creating membrane profile for {userid}")
        profile = pi_api.userprofile.create(userid, approve=True, properties=properties)
        sync_view = api.content.get_view("sync", profile, getRequest())
        # We need __call__(), not _sync(), in order to mock that Enumerate_Users
        # and Properties are activated.
        sync_view()


def on_user_transition(ob, event):
    # Avoid duuplicate username errors when use_email_as_userid is active
    user = event.object
    if event.new_state.id == "enabled":
        if user.email and user.email.startswith("DISABLED-"):
            user.email = user.email.replace("DISABLED-", "", 1)
            user.reindexObject()
            logger.info("Principal email re-enabled: %r", user.email)
    elif event.new_state.id == "disabled":
        if user.email and not user.email.startswith("DISABLED-"):
            user.email = f"DISABLED-{user.email}"
            user.reindexObject()
            logger.info("Principal email disabled: %r", user.email)

    # All enabled users are added to the intranet group
    if event.new_state.id != "enabled":
        return
    intranet = pi_api.workgroup.get("intranet")
    if intranet is None:
        logger.error("Required workgroup 'intranet' is missing.")
        return
    if user.getId() not in intranet.members:
        intranet.members.add(user.getId())
        notify(ObjectModifiedEvent(intranet))


def _get_pas_view(obj=None, request=None):
    request = request or getRequest() or getattr(obj, "REQUEST", None)
    try:
        return api.content.get_view("pas_view", api.portal.get(), request)
    except api.exc.InvalidParameterError:
        logger.warning("You might need to update PAS graph on your own")
        return


def on_membrane_principal_created(obj, event=None):
    """When a membrane principal has been added we add it to the pas_graph"""
    view = _get_pas_view(obj)
    view and view.add(obj)


def on_membrane_principal_modified(obj, event=None):
    """When a membrane principal has been added we add it to the pas_graph"""
    view = _get_pas_view(obj)
    view and view.update(obj)


def on_membrane_principal_removed(obj, event=None):
    """When a membrane principal has been added we add it to the pas_graph"""
    view = _get_pas_view(obj)
    view and view.remove(obj)


def _remove_local_owner_role(obj, userid):
    # Explicitly don't use PAS, but only check for the local role provided
    # directly via AccessControl's RoleManager
    if userid not in obj.users_with_local_role("Owner"):
        return
    obj.manage_delLocalRoles([userid])
    obj.reindexObject()


def on_user_added(obj, event=None):
    """Set up a placeful workflow policy if missing"""
    if WorkflowPolicyConfig_id in obj:
        return

    cmfpw = "CMFPlacefulWorkflow"
    obj.manage_addProduct[cmfpw].manage_addWorkflowPolicyConfig()
    pc = getattr(obj, WorkflowPolicyConfig_id)
    pc.setPolicyBelow("userprofile_policy")
    userid = api.user.get_current().id
    _remove_local_owner_role(obj, userid)
