from AccessControl import Unauthorized
from json import loads
from plone import api
from ploneintranet.workspace.behaviors.group import IMembraneGroupMarker
from ploneintranet.workspace.config import SecretWorkspaceNotAllowed
from ploneintranet.workspace.testing import (
    PLONEINTRANET_WORKSPACE_GROUPSPACE_INTEGRATION_TESTING,
)
from ploneintranet.workspace.tests.base import BaseTestCase
from Products.membrane.interfaces import IGroup
from zope.annotation import IAnnotations


class TestGroupspaceBehavior(BaseTestCase):
    """
    Test the abilities of users within a workspace
    """

    layer = PLONEINTRANET_WORKSPACE_GROUPSPACE_INTEGRATION_TESTING

    def setUp(self):
        super(self.__class__, self).setUp()
        # enable the quaive.group behavior
        pt = api.portal.get_tool("portal_types")
        fti = pt["ploneintranet.workspace.workspacefolder"]
        fti._updateProperty("behaviors", fti.behaviors[:] + ("quaive.group",))
        self.workspace_a = self.portal.workspaces["workspace-a"]
        self.workspace_b = self.portal.workspaces["workspace-b"]
        self.workspace_c = self.portal.workspaces["workspace-c"]

    def logout(self):
        """
        Delete any cached localrole information from the request.
        """
        super().logout()
        annotations = IAnnotations(self.request)
        keys_to_remove = []
        for key in annotations.keys():
            if isinstance(key, str) and key.startswith("borg.localrole"):
                keys_to_remove.append(key)

        for key in keys_to_remove:
            annotations.pop(key)

    def traverse_to_item(self, item):
        """helper method to travers to an item by path"""
        return api.content.get(path="/".join(item.getPhysicalPath()))

    def test_groups_behavior_present(self):
        self.assertTrue(IMembraneGroupMarker.providedBy(self.workspace_a))

    def test_group_interface_provided(self):
        self.assertTrue(IGroup(self.workspace_a, None) is not None)

    def test_group_title(self):
        obj = IGroup(self.workspace_a)
        self.assertEqual(obj.getGroupName(), self.workspace_a.Title())

    def test_basic_group_membership(self):
        obj = IGroup(self.workspace_a)
        self.assertEqual(set(obj.getGroupMembers()), {"johndoe", "admin"})

    def test_group_permissions_from_workspace(self):
        """
        Johndoe is member in Workspace A,
        Workspace A is member in Workspace B
            => Johndoe can access Workspace B
        """
        self.login("johndoe")
        # johndoe cannot access workspace-b
        with self.assertRaises(Unauthorized):
            self.traverse_to_item(self.workspace_b)
        self.logout()
        self.login_as_portal_owner()
        # the group workspace-a gets added as member to workspace-b
        self.add_user_to_workspace("workspace-a", self.workspace_b)
        obj = IGroup(self.workspace_b)
        self.assertEqual(set(obj.getGroupMembers()), {"workspace-a", "admin"})
        self.logout()
        # johndoe can now access workspace-b
        self.login("johndoe")
        self.traverse_to_item(self.workspace_b)
        self.logout()
        # but janeschmo still cannot
        self.login("janeschmo")
        with self.assertRaises(Unauthorized):
            self.traverse_to_item(self.workspace_b)

    def test_group_permissions_from_workspace_transitive(self):
        """
        Johndoe is member in Workspace A,
        Workspace A is member in Workspace B,
        Workspace B is member in Workspace C
            => Johndoe can access Workspace C
        """
        self.login("johndoe")
        # johndoe cannot access workspace-c
        with self.assertRaises(Unauthorized):
            self.traverse_to_item(self.workspace_c)
        self.logout()
        self.login_as_portal_owner()
        # the group workspace-a gets added as member to workspace-b
        self.add_user_to_workspace("workspace-a", self.workspace_b)
        obj = IGroup(self.workspace_b)
        self.assertEqual(set(obj.getGroupMembers()), {"workspace-a", "admin"})
        # the group workspace-b gets added as member to workspace-c
        self.add_user_to_workspace("workspace-b", self.workspace_c)
        obj = IGroup(self.workspace_c)
        self.assertEqual(set(obj.getGroupMembers()), {"workspace-b", "admin"})
        self.logout()
        # johndoe can now access workspace-c
        self.login("johndoe")

        self.traverse_to_item(self.workspace_c)
        self.logout()
        # but janeschmo still cannot
        self.login("janeschmo")
        with self.assertRaises(Unauthorized):
            self.traverse_to_item(self.workspace_c)

    def test_group_permissions_from_workspace_recursive(self):
        """
        Johndoe is member in Workspace A,
        Janeschmo is member in Workspace B,
        Workspace A is member in Workspace B,
        Workspace B is member in Workspace A:
            => Johndoe can access both workspaces
            => Janeschmo can access both workspaces
        """
        self.login_as_portal_owner()
        # john is already in A
        # Add Jane to B.
        self.add_user_to_workspace("janeschmo", self.workspace_b)
        # the group workspace-a gets added as member to workspace-b
        self.add_user_to_workspace("workspace-a", self.workspace_b)
        # the group workspace-b gets added as member to workspace-a
        self.add_user_to_workspace("workspace-b", self.workspace_a)
        obj = IGroup(self.workspace_a)
        self.assertEqual(
            set(obj.getGroupMembers()),
            {"workspace-b", "admin", "johndoe"},
            "Workspace A membership incorrect",
        )
        obj = IGroup(self.workspace_b)
        self.assertEqual(
            set(obj.getGroupMembers()),
            {"workspace-a", "admin", "janeschmo"},
            "Workspace B membership incorrect",
        )
        self.logout()
        # johndoe can now access workspace-a and workspace-b
        self.login("johndoe")
        self.traverse_to_item(self.workspace_a)
        self.traverse_to_item(self.workspace_b)
        self.logout()
        # janeschmo can also access both workspaces
        self.login("janeschmo")
        self.traverse_to_item(self.workspace_a)
        self.traverse_to_item(self.workspace_b)

    def test_group_permissions_from_workspace_recursive_transitive(self):
        """
        Johndoe is member in Workspace A,
        Janeschmo is member in Workspace B,
        Workspace A is member in Workspace B,
        Workspace B is member in Workspace C,
        Workspace C is member in Workspace A:
            => Johndoe can access all workspaces
            => Janeschmo can access all workspaces
        """
        self.login_as_portal_owner()
        # john is already in A
        # Add Jane to B.
        self.add_user_to_workspace("janeschmo", self.workspace_b)
        # the group workspace-a gets added as member to workspace-b
        self.add_user_to_workspace("workspace-a", self.workspace_b)
        # the group workspace-b gets added as member to workspace-c
        self.add_user_to_workspace("workspace-b", self.workspace_c)
        # make c visible to intranet users, so it can be added
        self.workspace_c.visible_to_intranet_users = True
        # the group workspace-c gets added as member to workspace-a
        self.add_user_to_workspace("workspace-c", self.workspace_a)
        obj = IGroup(self.workspace_a)
        self.assertEqual(
            set(obj.getGroupMembers()),
            {"workspace-c", "admin", "johndoe"},
            "Workspace A membership incorrect",
        )
        obj = IGroup(self.workspace_b)
        self.assertEqual(
            set(obj.getGroupMembers()),
            {"workspace-a", "admin", "janeschmo"},
            "Workspace B membership incorrect",
        )
        obj = IGroup(self.workspace_c)
        self.assertEqual(
            set(obj.getGroupMembers()),
            {"workspace-b", "admin"},
            "Workspace C membership incorrect",
        )
        self.logout()
        # johndoe can now access all 3 workspaces
        self.login("johndoe")
        self.traverse_to_item(self.workspace_a)
        self.traverse_to_item(self.workspace_b)
        self.traverse_to_item(self.workspace_c)
        self.logout()
        # janeschmo can also access all 3 workspaces
        self.login("janeschmo")
        self.traverse_to_item(self.workspace_a)
        self.traverse_to_item(self.workspace_b)
        self.traverse_to_item(self.workspace_c)

    def test_group_properties_provider(self):
        """
        Makes sure that our MembraneGroupProperties provider gets used
        """
        all_groups = api.group.get_groups()
        ws_groups = [
            group for group in all_groups if group.getId().startswith("workspace-")
        ]
        workspace_titles = {group.getProperty("title") for group in ws_groups}
        self.assertEqual(
            workspace_titles, {"Workspace A", "Workspace 𝔅", "Workspace 𝒞"}
        )
        workspace_uids = {group.getProperty("uid") for group in ws_groups}
        self.assertEqual(
            workspace_uids,
            {self.workspace_a.UID(), self.workspace_b.UID(), self.workspace_c.UID()},
        )

    def test_user_enumeration(self):
        """Check our list of user is correct"""
        users = api.user.get_users()
        self.assertListEqual(
            sorted(user.getId() for user in users),
            ["admin", "demo", "janeschmo", "johndoe", "test_user_1_"],
        )

    def test_workspace_members_listing_ignores_self(self):
        self.login_as_portal_owner()
        json_groups = self.workspace_a.restrictedTraverse("allgroups.json")()
        available_group_ids = [x["id"] for x in loads(json_groups)]
        # Workspace A does not list itself as a candidate
        self.assertFalse("workspace-a" in available_group_ids)

    def test_workspace_members_listing_shows_candidate(self):
        self.login_as_portal_owner()
        json_groups = self.workspace_a.restrictedTraverse("allgroups.json")()
        available_group_ids = [x["id"] for x in loads(json_groups)]
        self.assertTrue("workspace-b" in available_group_ids)

    def test_workspace_members_listing_ignores_secret(self):
        self.login_as_portal_owner()
        json_groups = self.workspace_a.restrictedTraverse("allgroups.json")()
        available_group_ids = [x["id"] for x in loads(json_groups)]
        # The secret workspace C does not get listed
        self.assertFalse("workspace-c" in available_group_ids)

    def test_secret_workspace_cannot_be_added(self):
        with self.assertRaises(SecretWorkspaceNotAllowed):
            # Add secret workpace C to A - this is not allowed
            self.add_user_to_workspace("workspace-c", self.workspace_a)
