from logging import getLogger
from plone import api
from ploneintranet import api as pi_api
from ploneintranet.userprofile.interfaces import INeverDeletedMemberGroup
from ploneintranet.userprofile.pas.pi_localroles import PILocalRoleManager
from ploneintranet.userprofile.pas.pi_recursive_groups import PIRecursiveGroupsPlugin
from ploneintranet.userprofile.pas.pi_userfactory import PIUserFactory
from ploneintranet.workspace.config import INTRANET_USERS_GROUP_ID
from Products.MemcachedManager.MemcachedManager import manage_addMemcachedManager
from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin
from Products.PluggableAuthService.interfaces.plugins import IGroupsPlugin
from Products.PluggableAuthService.interfaces.plugins import IUserFactoryPlugin
from zope.interface import alsoProvides

import socket

logger = getLogger(__name__)


def get_or_create_userprofile_container(context=None):
    """We want to have a profiles folder in the plone site root
    We want it public

    The context parameter is ignored
    It depends on wheter this function is called as an upgrade
    or an import step
    We use plone api to get the portal.
    """
    portal = api.portal.get()
    try:
        container = portal.contentValues(
            {"portal_type": "ploneintranet.userprofile.userprofilecontainer"}
        )[0]
    except IndexError:
        logger.info("Creating user profile container")
        container = api.content.create(
            id="profiles",
            title="Profiles",
            type="ploneintranet.userprofile.userprofilecontainer",
            container=portal,
        )

    if container.getId() != "profiles":
        logger.warning(
            'The id for user profile container is "%s" and not "profiles"',
            container.getId(),
        )

    if api.content.get_state(container) != "published":
        logger.info("Publishing the user profile container %s", container.getId())
        try:
            api.content.transition(container, "publish")
        except BaseException:
            logger.exception(
                "Cannot publish the user profile container %s", container.getId()
            )
            try:
                api.content.transition(container, "publish_internally")
            except BaseException:
                logger.exception(
                    "Cannot publish the user profile container %s internally",
                    container.getId(),
                )
    return container


def get_or_create_workgroup_container():
    portal = api.portal.get()
    if "groups" not in portal:
        container = api.content.create(
            id="groups",
            container=portal,
            type="ploneintranet.userprofile.workgroupcontainer",
            title="Groups",
        )
        try:
            api.content.transition(container, "publish")
        except BaseException:
            logger.exception(
                "Cannot publish the workgroup container %s", container.getId()
            )
            try:
                api.content.transition(container, "publish_internally")
            except BaseException:
                logger.exception(
                    "Cannot publish the workgroup container %s internally",
                    container.getId(),
                )
    return portal.groups


def create_intranet_group(context=None):
    groups = get_or_create_workgroup_container()
    if "intranet" not in groups:
        pi_api.workgroup.create(
            groupid="intranet",
            canonical=INTRANET_USERS_GROUP_ID,
            title=INTRANET_USERS_GROUP_ID,
        )
        alsoProvides(groups.intranet, INeverDeletedMemberGroup)
        # Ensure the group is indexed in membrane
        mt = api.portal.get_tool("membrane_tool")
        mt.indexObject(groups.intranet)
        api.group.grant_roles(groupname=INTRANET_USERS_GROUP_ID, roles=["Member"])


def update_dx_membrane_behaviors(context):
    """dexterity.membrane 1.1.0 changed the names of its behaviours"""
    from plone.dexterity.interfaces import IDexterityFTI
    from zope.component import getUtility

    fti = getUtility(IDexterityFTI, name="ploneintranet.userprofile.userprofile")
    old_behaviors = list(fti.behaviors)
    new_behaviors = []
    for behavior in old_behaviors:
        behavior = behavior.replace(
            "dexterity.membrane.behavior.membrane", "dexterity.membrane.behavior."
        )
        new_behaviors.append(behavior)
    fti.behaviors = tuple(new_behaviors)


def setup_pas_plugins(context=None):
    """Remove the recursive_groups plugin and add the pi_recursiveplugin"""
    au = api.portal.get_tool("acl_users")
    banned_ids = ("recursive_groups", "membrane_groups", "workspace_groups")
    for banned_id in banned_ids:
        try:
            au.plugins.deactivatePlugin(IGroupsPlugin, banned_id)
        except KeyError:
            pass  # Already deactivated

    if "pi_recursive_groups" not in au:
        rgp = PIRecursiveGroupsPlugin(
            "pi_recursive_groups", "Recursive group plugin for Ploneintranet"
        )
        au._setObject(rgp.getId(), rgp)
    if "pi_user_factory" not in au:
        piuf = PIUserFactory("pi_user_factory")
        au._setObject(piuf.getId(), piuf)
    if "pi_local_roles" not in au:
        pilrm = PILocalRoleManager("pi_local_roles")
        au._setObject(pilrm.getId(), pilrm)

    try:
        au.plugins.activatePlugin(IGroupsPlugin, "pi_recursive_groups")
    except KeyError:
        pass  # Already activated
    try:
        au.plugins.activatePlugin(IUserFactoryPlugin, "pi_user_factory")
    except KeyError:
        pass  # Already activated
    try:
        au.plugins.activatePlugin(ILocalRolesPlugin, "pi_local_roles")
    except KeyError:
        pass  # Already activated

    try:
        au.plugins.deactivatePlugin(IUserFactoryPlugin, "membrane_user_factory")
    except KeyError:
        pass  # Already deactivated
    try:
        au.plugins.deactivatePlugin(ILocalRolesPlugin, "borg_localroles")
    except KeyError:
        pass  # Already activated

    # Order plugins
    au.plugins._plugins[IUserFactoryPlugin] = tuple(
        sorted(
            au.plugins.listPluginIds(IUserFactoryPlugin),
            key=lambda x: x != "pi_user_factory",
        )
    )

    # initialize the pas graph
    portal = api.portal.get()
    portal.restrictedTraverse("pas-graph-init")()


def _setup_acl_users_cache():
    """Check if we have a memcached server running (at the standard location)
    and if do not already have the acl_users cached by an existing cache manager
    """
    au = api.portal.get_tool("acl_users")
    if au.ZCacheable_getManagerId() is not None:
        # Somebody already took care of setting a cachemanager
        return
    # Check if a memcached server is listening
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        if s.connect_ex(("127.0.0.1", 11211)) != 0:
            # Cant' connect, just exit with a suggestion
            logger.info(
                "Setting a cache for the acl_users folder is strongly recommended."
            )
            return

    portal = api.portal.get()
    if not portal.get("MemcachedManager"):
        # created the memcached manager if it is missing
        manage_addMemcachedManager(portal, "MemcachedManager")
    au.ZCacheable_setManagerId(manager_id="MemcachedManager")


def on_install(context):
    """
    Important!
    We do not want to have users with global roles such as Editor or
    Contributor in out test setup.
    """
    get_or_create_userprofile_container()
    create_intranet_group()
    setup_pas_plugins()
    _setup_acl_users_cache()
