from AccessControl import getSecurityManager
from plone import api
from plone.app.testing import login
from plone.app.testing import SITE_OWNER_NAME
from ploneintranet import api as pi_api
from ploneintranet.calendar.testing import IntegrationTestCase
from ploneintranet.userprofile.behaviors.maintainer_group import (
    MaintainerGroupListLocalRoles,
)
from ploneintranet.userprofile.behaviors.reader_group import ReaderGroupListLocalRoles
from z3c.relationfield.relation import RelationValue
from zope.component import getUtility
from zope.event import notify
from zope.intid.interfaces import IIntIds
from zope.lifecycleevent import ObjectModifiedEvent

import unittest


class FakeCalendar:
    def __init__(self, reader_group_list=set(), maintainer_group_list=set()):
        self.reader_group_list = reader_group_list
        self.maintainer_group_list = maintainer_group_list


class TestCalendarMaintainerRoleAdapter(unittest.TestCase):
    """ """

    def setUp(self):
        self.obj = FakeCalendar()
        self.role_adapter = MaintainerGroupListLocalRoles(self.obj)

    def test_no_roles(self):
        self.assertListEqual(self.role_adapter.getRoles("hr"), [])
        self.assertListEqual(self.role_adapter.getAllRoles(), [])

    def test_maintainable_by_hr(self):
        self.obj.maintainer_group_list = {"hr"}
        self.assertListEqual(self.role_adapter.getRoles("other_group"), [])
        self.assertSetEqual(
            set(self.role_adapter.getRoles("hr")),
            {"Reader", "Editor", "Reviewer", "Contributor"},
        )
        self.assertListEqual(
            [
                (group, sorted(permissions))
                for group, permissions in self.role_adapter.getAllRoles()
            ],
            [("hr", sorted(["Reader", "Editor", "Reviewer", "Contributor"]))],
        )


class TestCalendarReaderRoleAdapter(unittest.TestCase):
    """ """

    def setUp(self):
        self.obj = FakeCalendar()
        self.role_adapter = ReaderGroupListLocalRoles(self.obj)

    def test_no_roles(self):
        self.assertListEqual(self.role_adapter.getRoles("hr"), [])
        self.assertListEqual(self.role_adapter.getAllRoles(), [])

    def test_visible_to_hr(self):
        self.obj.reader_group_list = {"hr"}
        self.assertListEqual(self.role_adapter.getRoles("other_group"), [])
        self.assertListEqual(self.role_adapter.getRoles("hr"), ["Reader"])
        self.assertListEqual(self.role_adapter.getAllRoles(), [("hr", ["Reader"])])


class TestCalendarPermissions(IntegrationTestCase):
    def setUp(self):
        super().setUp()
        login(self.layer["app"], SITE_OWNER_NAME)
        self.my_user = pi_api.userprofile.create(
            username="my_user",
            email="my_user@intranet.org",
            properties={"fullname": "My User"},
        )
        self.portal.groups.intranet.members.add(self.my_user.getId())
        notify(ObjectModifiedEvent(self.portal.groups.intranet))

        self.calendar_managers = pi_api.workgroup.create(
            groupid="calendar-managers", title="Calendar Managers"
        )
        api.group.grant_roles(
            groupname="calendar-managers",
            obj=self.portal.calendars,
            roles=["Contributor", "Editor", "Reader"],
        )
        self.calendar_managers.members.add("my_user")
        notify(ObjectModifiedEvent(self.calendar_managers))

        self.other_user = pi_api.userprofile.create(
            username="other_user", email="other_user@intranet.org"
        )
        self.portal.groups.intranet.members.add(self.other_user.getId())
        notify(ObjectModifiedEvent(self.portal.groups.intranet))

        self.hr_group = pi_api.workgroup.create(groupid="hr", title="HR")
        self.hr_member = pi_api.userprofile.create(
            username="hr_member", email="hr_member@intranet.org"
        )
        self.hr_group.members.add("hr_member")
        self.hr_group.reindexObject()
        notify(ObjectModifiedEvent(self.hr_group))

        self.production_group = pi_api.workgroup.create(
            groupid="production", title="Production"
        )
        self.production_member = pi_api.userprofile.create(
            username="production_member", email="production_member@intranet.org"
        )
        self.production_group.members.add("production_member")
        notify(ObjectModifiedEvent(self.production_group))

        login(self.layer["portal"], self.my_user.getId())
        self.existing_calendar = api.content.create(
            type="ploneintranet.calendar.central_calendar",
            title="Existing Calendar",
            container=self.portal.calendars,
        )

    def test_assign_no_groups_on_create(self):
        login(self.layer["portal"], self.my_user.getId())
        app = self.portal.get("apps").get("calendar")
        request = self.request.clone()
        request["method"] = "POST"
        request.form = {
            "portal_type": "ploneintranet.calendar.central_calendar",
            "title": "Test Calendar",
            "description": "Test",
            "app": "http://foo/apps/calendar",
            "container": "/plone/calendars",
            "maintenance": "groups",
            "maintainer_group_list": "",
            "maintainer_group_list-empty-marker": "1",
            "visibility": "all",
            "reader_group_list": "",
            "reader_group_list-empty-marker": "1",
            "allow_contributions-empty-marker": "1",
            "automatically_accept-empty-marker": "1",
            "form.buttons.create": "Create",
        }
        add_view = api.content.get_view(context=app, request=request, name="add_event")
        add_view()
        calendars = self.portal.get("calendars")
        calendar = calendars.get("test-calendar")

        for permission in ["View", "Modify portal content", "Add portal content"]:
            self.assertTrue(
                api.user.has_permission(permission, user=self.my_user, obj=calendar),
                msg=f'Owner does not have "{permission}" permission',
            )
            self.assertFalse(
                api.user.has_permission(permission, user=self.other_user, obj=calendar),
                msg=f'Other user has "{permission}" permission',
            )

    def test_assign_specific_groups_on_create(self):
        login(self.layer["portal"], self.my_user.getId())
        app = self.portal.get("apps").get("calendar")
        request = self.request.clone()
        request["method"] = "POST"
        request.form = {
            "portal_type": "ploneintranet.calendar.central_calendar",
            "title": "Test Calendar",
            "description": "Test",
            "app": "http://foo/apps/calendar",
            "container": "/plone/calendars",
            "maintenance": "groups",
            "maintainer_group_list": "hr,production",
            "maintainer_group_list-empty-marker": "1",
            "visibility": "all",
            "reader_group_list": "hr,production",
            "reader_group_list-empty-marker": "1",
            "allow_contributions-empty-marker": "1",
            "automatically_accept-empty-marker": "1",
            "form.buttons.create": "Create",
        }
        setattr(self.layer["app"], "REQUEST", request)
        add_view = api.content.get_view(context=app, request=request, name="add_event")
        add_view()
        calendars = self.portal.get("calendars")
        calendar = calendars.get("test-calendar")

        for permission in ["View", "Modify portal content", "Add portal content"]:
            self.assertTrue(
                api.user.has_permission(permission, user=self.my_user, obj=calendar),
                msg=f'Owner does not have "{permission}" permission',
            )
            self.assertFalse(
                api.user.has_permission(permission, user=self.other_user, obj=calendar),
                msg=f'Other user has "{permission}" permission',
            )
        self.assertTrue(
            api.user.has_permission("View", user=self.production_member, obj=calendar),
            msg='reader_group member does not have "View" permission',
        )
        for permission in ["View", "Modify portal content", "Add portal content"]:
            self.assertTrue(
                api.user.has_permission(permission, user=self.hr_member, obj=calendar),
                msg=(
                    'maintainer_group member does not have "{}" permission'
                    "".format(permission)
                ),
            )

    def test_assign_combination_on_create(self):
        login(self.layer["portal"], self.my_user.getId())
        app = self.portal.get("apps").get("calendar")
        request = self.request.clone()
        request["method"] = "POST"
        request.form = {
            "portal_type": "ploneintranet.calendar.central_calendar",
            "title": "Test Calendar",
            "description": "Test",
            "app": "http://foo/apps/calendar",
            "container": "/plone/calendars",
            "maintenance": "groups",
            "maintainer_group_list": "hr,production",
            "maintainer_group_list-empty-marker": "1",
            "visibility": "all",
            "reader_group_list": "",
            "reader_group_list-empty-marker": "1",
            "allow_contributions-empty-marker": "1",
            "automatically_accept-empty-marker": "1",
            "form.buttons.create": "Create",
        }
        setattr(self.layer["app"], "REQUEST", request)
        add_view = api.content.get_view(context=app, request=request, name="add_event")
        add_view()
        calendars = self.portal.get("calendars")
        calendar = calendars.get("test-calendar")

        for permission in ["View", "Modify portal content", "Add portal content"]:
            self.assertTrue(
                api.user.has_permission(permission, user=self.my_user, obj=calendar),
                msg=f'Owner does not have "{permission}" permission',
            )
            self.assertFalse(
                api.user.has_permission(permission, user=self.other_user, obj=calendar),
                msg=f'Other user has "{permission}" permission',
            )
        for permission in ["View", "Modify portal content", "Add portal content"]:
            self.assertTrue(
                api.user.has_permission(permission, user=self.hr_member, obj=calendar),
                msg=(
                    'maintainer_group member does not have "{}" permission'
                    "".format(permission)
                ),
            )


class TestSharedEventsPermissions(IntegrationTestCase):
    def test_shared_event_permission(self):
        """Check that the user can view an event placed in a workspace
        that he cannot access if the event is shared to a calendar
        that the user is allowed to view
        """
        with api.env.adopt_user("admin"):
            event = api.content.create(
                title="Test Event", container=self.workspace, type="Event"
            )
            api.content.transition(obj=event, transition="submit")
            calendar = api.content.create(
                container=self.portal.calendars,
                title="My calendar",
                type="ploneintranet.calendar.central_calendar",
            )
            pi_api.userprofile.create("johndoe", approve=True)

        intids = getUtility(IIntIds)
        cal_intid = intids.getId(calendar)
        event.shared_calendar = RelationValue(cal_intid)
        notify(ObjectModifiedEvent(event))
        has_permission = api.user.has_permission

        # At this point the user cannot access the workspace AND the event
        # shared to the calendar
        self.request.__annotations__.clear()
        with api.env.adopt_user("johndoe"):
            self.assertFalse(has_permission(permission="View", obj=self.workspace))
            self.assertFalse(has_permission(permission="View", obj=event))

        # But if we share the event to a group he belongs to...
        calendar.reader_group_list = {self.portal.groups.intranet.canonical}
        notify(ObjectModifiedEvent(calendar))

        # ... he still cannot visit the workspace but can visit the event
        self.request.__annotations__.clear()
        with api.env.adopt_user("johndoe"):
            self.assertFalse(has_permission(permission="View", obj=self.workspace))
            self.assertTrue(has_permission(permission="View", obj=event))

    def test_workflow_guards(self):
        """Check that the workflow guards are working as expected"""
        with api.env.adopt_user("admin"):
            calendar = api.content.create(
                container=self.portal.calendars,
                title="My calendar",
                type="ploneintranet.calendar.central_calendar",
            )
            intids = getUtility(IIntIds)
            cal_intid = intids.getId(calendar)
            event = api.content.create(
                title="Test Event",
                container=self.workspace,
                type="Event",
                shared_calendar=RelationValue(cal_intid),
            )

        pw = api.portal.get_tool("portal_workflow")

        sm = getSecurityManager()
        wf = pw.getWorkflowsFor(event)[0]

        self.assertTrue(wf.transitions.submit.guard.check(sm, wf, event))
        self.assertFalse(wf.transitions.accept.guard.check(sm, wf, event))
        self.assertFalse(wf.transitions.auto_accept.guard.check(sm, wf, event))
        self.assertFalse(wf.transitions.reject.guard.check(sm, wf, event))
        self.assertTrue(wf.transitions.retract.guard.check(sm, wf, event))
