from AccessControl import Unauthorized
from collective.workspace.interfaces import IWorkspace
from importlib.resources import files
from plone import api
from plone.namedfile.file import NamedBlobImage
from ploneintranet import api as pi_api
from ploneintranet.layout.browser.actions import PIDeleteConfirmationForm
from ploneintranet.workspace.browser.delete_confirmation import TrashConfirmationForm
from ploneintranet.workspace.browser.tiles.sidebar import Sidebar
from ploneintranet.workspace.browser.tiles.sidebar import SidebarSettingsAdvanced
from ploneintranet.workspace.browser.views import BaseFileUploadView
from ploneintranet.workspace.browser.views import FileUploadView
from ploneintranet.workspace.browser.views import JoinView
from ploneintranet.workspace.browser.views import SharingView
from ploneintranet.workspace.config import INTRANET_USERS_GROUP_ID
from ploneintranet.workspace.interfaces import ITrashable
from ploneintranet.workspace.interfaces import ITrashed
from ploneintranet.workspace.interfaces import IWorkspaceAppContentLayer
from ploneintranet.workspace.tests.base import BaseTestCase
from ploneintranet.workspace.tests.base import FunctionalBaseTestCase
from unittest import mock
from zope.event import notify
from zope.interface import alsoProvides
from zope.lifecycleevent import ObjectModifiedEvent


class BaseViewTest(BaseTestCase):
    def setUp(self):
        super().setUp()
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]

        self.login_as_portal_owner()
        self.workspace = self.portal.workspaces["demo-workspace"]
        self.user = api.user.get(username="demo")


class TestWorkspaceView(BaseViewTest):
    def test_get_principal_roles(self):
        view = api.content.get_view("view", self.workspace, self.request.clone())
        admin = api.user.get("admin")
        self.assertListEqual(view.get_principal_roles(admin), ["Admin"])

        IWorkspace(self.workspace).add_to_team("demo")

        demo = api.user.get("demo")
        self.assertListEqual(view.get_principal_roles(demo), [])
        self.assertListEqual(
            view.get_principal_roles(demo, omit_default_policy_role=False), ["Consume"]
        )

    def test_percent_complete(self):
        case = api.content.create(
            container=self.portal.workspaces,
            type="ploneintranet.workspace.workspacefolder",
            title="A sample case",
        )
        view = api.content.get_view("view", case, self.request.clone())
        # We have no tasks yet, we expect no info
        self.assertEqual(view.percent_complete(), "")
        todo1 = api.content.create(container=case, type="todo", id="todo1")
        # we have one task now but the result is memoized
        self.assertEqual(view.percent_complete(), "")
        view.request = self.request.clone()
        self.assertEqual(view.percent_complete(), "0%")
        # We close outr task
        api.content.transition(obj=todo1, to_state="done")
        view.request = self.request.clone()
        self.assertEqual(view.percent_complete(), "100%")
        # If we create other tasks the percent complete value will vary
        api.content.create(container=case, type="todo", id="todo2")
        view.request = self.request.clone()
        self.assertEqual(view.percent_complete(), "50%")
        api.content.create(container=case, type="todo", id="todo3")
        view.request = self.request.clone()
        self.assertEqual(view.percent_complete(), "33%")

    def test_get_workspace_security_icon(self):
        view = api.content.get_view("view", self.workspace, self.request.clone())
        # Regular secret workspaces are secret
        self.assertEqual(view.get_workspace_security_icon(), "icon-user-secret")

        # If they are visible they look as locked
        view.context.visible_to_intranet_users = True
        self.assertEqual(view.get_workspace_security_icon(), "icon-lock")

        # If they are accessible the lock is open
        view.context.accessible_to_intranet_users = True
        self.assertEqual(view.get_workspace_security_icon(), "icon-lock-open")

        # This wins over visibility
        view.context.visible_to_intranet_users = False
        self.assertEqual(view.get_workspace_security_icon(), "icon-lock-open")


class TestWorkspaceSettings(BaseViewTest):
    def test_set_attributes(self):
        self.request.method = "POST"
        self.request.form = {
            "title": "Settings Test",
            "description": "attr write test",
            "calendar_visible": "selected",
            "email": "tester@testorg.net",
        }
        self.request["HTTP_REFERER"] = "someurl"
        self.login_as_portal_owner()
        view = Sidebar(self.workspace, self.request)
        self.assertNotEqual(self.workspace.title, "Settings Test")
        self.assertNotEqual(self.workspace.description, "attr write test")
        self.assertNotEqual(self.workspace.calendar_visible, True)
        self.assertNotEqual(self.workspace.email, "tester@testorg.net")
        view()
        self.assertEqual(self.workspace.title, "Settings Test")
        self.assertEqual(self.workspace.description, "attr write test")
        self.assertEqual(self.workspace.calendar_visible, True)
        self.assertEqual(self.workspace.email, "tester@testorg.net")

        self.request.form = {"email": "tester2@testorg.net"}
        # By now, request.email is also set to the above value, need
        # to rewrite explicitly
        self.request["email"] = "tester2@testorg.net"
        view = SidebarSettingsAdvanced(self.workspace, self.request)
        self.assertNotEqual(self.workspace.email, "tester2@testorg.net")
        view()
        self.assertEqual(self.workspace.email, "tester2@testorg.net")

    def test_infographic(self):
        """XXX extend the coverage"""
        self.workspace.infographic_image = NamedBlobImage(
            data=(
                files("ploneintranet.workspace.tests") / "infographic.svg"
            ).read_bytes(),
            filename="infographic.svg",
        )
        folder = api.content.create(
            container=self.workspace, type="Folder", id="a-folder"
        )
        doc = api.content.create(
            container=self.workspace, type="Document", id="a-document"
        )
        # the keys of this dictionary are the ids written in infographic.svg
        self.workspace.hotspots = {
            "hotspot-triangle": folder.UID(),
            "hotspot-square": doc.UID(),
        }

        view = api.content.get_view("workspace-leader", self.workspace, self.request)
        self.assertDictEqual(
            view.hotspot_mapping, {"hotspot-triangle": folder, "hotspot-square": doc}
        )
        self.assertIn(".on-hotspot-clicked", view.infographic_with_links)


class TestSelfJoin(BaseViewTest):
    def open_workspace(self):
        self.workspace.join_policy = "self"
        self.workspace.visibility = "open"

    def test_user_can_join(self):
        self.open_workspace()
        self.request.method = "POST"
        self.request.form = {"button.join": True}
        self.request["HTTP_REFERER"] = "someurl"
        self.login("demo")
        view = JoinView(self.workspace, self.request)
        response = view()
        self.assertEqual("someurl", response)
        self.assertIn("demo", IWorkspace(self.workspace).members)

    def test_user_cant_join_if_policy_is_not_self(self):
        self.login("demo")
        view = JoinView(self.workspace, self.request)
        self.assertRaises(Unauthorized, view)

    def test_user_redirected_if_method_get(self):
        self.open_workspace()
        self.request.method = "GET"
        self.request.form = {"button.join": True}
        self.request["HTTP_REFERER"] = "someurl"
        self.login("demo")
        view = JoinView(self.workspace, self.request)
        response = view()
        self.assertEqual("someurl", response)
        self.assertNotIn("demo", IWorkspace(self.workspace).members)

    def test_user_redirected_to_workspace_if_no_referer(self):
        self.open_workspace()
        self.login("demo")
        view = JoinView(self.workspace, self.request)
        response = view()
        self.assertEqual(self.workspace.absolute_url(), response)


class TestSharingView(BaseViewTest):
    def test_inherit_view_is_not_shown(self):
        self.login_as_portal_owner()
        view = SharingView(self.workspace, self.request)
        self.assertFalse(view.can_edit_inherit())

    def test_sharing_view_filters_groups(self):
        self.login_as_portal_owner()
        view = SharingView(self.workspace, self.request)
        ids = [group["id"] for group in view.role_settings()]
        self.assertNotIn("AuthenticatedUsers", ids)
        uid = self.workspace.UID()
        self.assertEqual([x for x in ids if x.endswith(uid)], [])

    def test_member_is_added_to_user_title_if_user_is_a_member(self):
        self.login_as_portal_owner()
        IWorkspace(self.workspace).add_to_team(self.user.getId())
        self.request.form = {"form.button.Search": "Search", "search_term": "demo"}
        view = SharingView(self.workspace, self.request)
        self.assertIn(f"{self.user.getId()} [member]", view())

    def test_member_is_not_added_if_user_is_not_a_member(self):
        self.login_as_portal_owner()
        self.request.form = {"form.button.Search": "Search", "search_term": "demo"}
        view = SharingView(self.workspace, self.request)
        self.assertNotIn(f"{self.user.getId()} [member]", view())

    def test_administrator_is_added_to_administrator(self):
        """Test that [administrator] is added to the workspace
        administrator"""
        self.login_as_portal_owner()
        IWorkspace(self.workspace).add_to_team(self.user.getId(), groups={"Admins"})
        self.request.form = {"form.button.Search": "Search", "search_term": "demo"}
        view = SharingView(self.workspace, self.request)
        self.assertIn(f"{self.user.getId()} [administrator]", view())
        self.assertNotIn(f"{self.user.getId()} [member]", view())

    def test_acquired_roles_from_policy_settings(self):
        self.login_as_portal_owner()
        policy = "moderators"
        self.workspace.participant_policy = policy
        ws = IWorkspace(self.workspace)
        ws.add_to_team(self.user.getId())
        roles = ws.available_groups.get(policy.title())
        self.request.form = {"form.button.Search": "Search", "search_term": "demo"}
        view = SharingView(self.workspace, self.request)
        results = view.user_search_results()

        self.assertEqual(len(results), 1)
        self.assertTrue(
            all([results[0]["roles"][role] == "acquired" for role in roles]),
            "Acquired roles were not set correctly",
        )


class TestFileUploadView(BaseViewTest):
    def setUp(self):
        super().setUp()
        self.view = FileUploadView(self.workspace, self.request)

    def test_no_file_json(self):
        self.request.environ["HTTP_ACCEPT"] = "application/json"
        self.assertEqual(self.view(), "[]")

    def test_no_file_html(self):
        self.request.environ["HTTP_ACCEPT"] = "text/html"
        self.assertEqual(self.view(), None)
        self.assertEqual(self.request.response.getStatus(), 302)

    def test_upload_file_json(self):
        self.request.environ["HTTP_ACCEPT"] = "application/json"
        self.request.form = {"file": "dummy"}
        with mock.patch.object(BaseFileUploadView, "__call__") as mock_call:
            mock_call.return_value = '{"type": "dummy"}'
            result = self.view()
            self.assertTrue(isinstance(result, str))
            self.assertNotEqual(result, "{}")

    def test_upload_file_html(self):
        self.request.environ["HTTP_ACCEPT"] = "text/html"
        self.request.form = {"file": "dummy"}
        with mock.patch.object(BaseFileUploadView, "__call__") as mock_call:
            mock_call.return_value = '{"type": "dummy"}'
            self.assertEqual(self.view(), None)
            self.assertEqual(self.request.response.getStatus(), 302)


class TestWorkspaceSidebar(BaseViewTest):
    def setUp(self):
        super().setUp()

    def test_subscribe_button_configurable(self):
        """There is a registry record
        (ploneintranet.workspace.allow_bulk_subscribe)
        that can be used to show the subscribe button from the sidebar
        bulk actions
        """
        view = api.content.get_view("sidebar.default", self.workspace, self.request)
        # By default the button is disabled
        self.assertNotIn("Subscribe</button>", view())

        # But we can show it
        api.portal.set_registry_record(
            "ploneintranet.workspace.allow_bulk_subscribe", True
        )
        self.assertIn("Subscribe</button>", view())

        # If there is no record, then the button is not shown
        pr = api.portal.get_tool("portal_registry")
        pr.records.__delitem__("ploneintranet.workspace.allow_bulk_subscribe")
        self.assertNotIn("Subscribe</button>", view())


class TestWorkspaceTrashcan(BaseViewTest):
    def test_trashable_types(self):
        """Check the items that are trashable"""
        pt = api.portal.get_tool("portal_types")
        self.assertListEqual(
            [
                fti.id
                for fti in pt.listTypeInfo()
                if "quaive.trashable" in getattr(fti, "behaviors", [])
            ],
            ["Document", "File", "Image", "ploneintranet.workspace.mail"],
        )

    def test_delete_confirmation(self):
        # Create a doc and an event and verify how they behave
        doc = api.content.create(
            title="Trashable", type="Document", container=self.workspace
        )
        evt = api.content.create(
            title="Trashable", type="Event", container=self.workspace
        )
        self.assertTrue(ITrashable.providedBy(doc))
        self.assertFalse(ITrashable.providedBy(evt))

        # If the request does not provide IWorkspaceAppContentLayer
        # we have the delete form customized for Plone Intranet
        req = self.request.clone()
        view = api.content.get_view("delete_confirmation", evt, req)
        self.assertIsInstance(view, PIDeleteConfirmationForm)

        req = self.request.clone()
        view = api.content.get_view("delete_confirmation", doc, req)
        self.assertIsInstance(view, PIDeleteConfirmationForm)

        # If the request does provide IWorkspaceAppContentLayer
        # we have a special delete form for the Trashable objects
        req = self.request.clone()
        alsoProvides(req, IWorkspaceAppContentLayer)
        view = api.content.get_view("delete_confirmation", evt, req)
        self.assertIsInstance(view, PIDeleteConfirmationForm)

        req = self.request.clone()
        alsoProvides(req, IWorkspaceAppContentLayer)
        view = api.content.get_view("delete_confirmation", doc, req)
        self.assertIsInstance(view, TrashConfirmationForm)
        self.assertTrue(view.trash_enabled)

        # Now if we trash the object we will have again the normal delete form
        view.recursively_mark_trashed()
        req = self.request.clone()
        alsoProvides(req, IWorkspaceAppContentLayer)
        view = api.content.get_view("delete_confirmation", doc, req)
        self.assertIsInstance(view, PIDeleteConfirmationForm)

    def test_recover_from_trash_multiple_levels(self):
        """This test shows that when we recover an object from the trash,
        we implicitly remove all its trashed parents
        """
        # We create a folder structure
        folder = api.content.create(
            title="Trashable Folder", type="Folder", container=self.workspace
        )
        doc = api.content.create(
            title="Trashable Document", type="Document", container=folder
        )
        event = api.content.create(
            title="Trashable Document", type="Event", container=folder
        )
        # Forcefully put everything in the trash can
        for obj in (folder, doc, event):
            alsoProvides(obj, ITrashed)
            self.assertTrue(ITrashed.providedBy(obj))

        # untrash the doc
        req = self.request.clone()
        alsoProvides(req, IWorkspaceAppContentLayer)
        view = api.content.get_view("untrash", doc, req)
        view()

        # this implicitly recovers the parents
        for obj in (folder, doc):
            self.assertFalse(ITrashed.providedBy(obj))

        # but not the sibling
        self.assertTrue(ITrashed.providedBy(event))


class TestWorkspacePanelUsers(BaseViewTest):
    def test_panel_user_filters(self):
        # Add a bunch of users
        wsadmin = pi_api.userprofile.create(username="wsadmin", email="admin@test.com")
        user1 = pi_api.userprofile.create(username="user1", email="test@test.com")
        self.portal.groups.intranet.members.update({"wsadmin", "user1"})
        notify(ObjectModifiedEvent(self.portal.groups.intranet))

        # Get the workspace view
        view = api.content.get_view("panel-users", self.workspace, self.request.clone())
        # check no one is mentionable
        self.assertListEqual(view.filters_users["UID"], [])

        # unless we have members
        view.request.__annotations__.clear()
        IWorkspace(self.workspace).add_to_team(wsadmin.getId())
        self.assertListEqual(view.filters_users["UID"], [wsadmin.UID()])
        # ... or guests ...
        view.request.__annotations__.clear()
        IWorkspace(view.context).add_to_team("user1", roles=["Guest"])
        self.assertSetEqual(
            set(view.filters_users["UID"]), {wsadmin.UID(), user1.UID()}
        )
        # ... or the ws is accessible to everybody (everybody is mentionable)
        view.request.__annotations__.clear()
        view.context.accessible_to_intranet_users = True
        self.assertNotIn("UID", view.filters_users)

    def test_intranet_users_excluded(self):
        view = api.content.get_view("panel-users", self.workspace, self.request.clone())
        self.assertNotIn(
            INTRANET_USERS_GROUP_ID, [res["Title"] for res in view.results]
        )


class TestFolderView(FunctionalBaseTestCase):
    def setUp(self):
        super().setUp()
        self.login_as_portal_owner()
        self.workspace_container = api.content.create(
            self.portal,
            "ploneintranet.workspace.workspacecontainer",
            "example-workspace-container",
        )
        self.workspace = api.content.create(
            self.workspace_container,
            "ploneintranet.workspace.workspacefolder",
            "example-workspace",
        )
        self.folder = api.content.create(self.workspace, "Folder", "example-folder")

        # Commit so the testbrowser can see the workspace
        import transaction

        transaction.commit()

    def test_sidebar_open_on_load(self):
        self.browser_login_as_site_administrator()
        self.browser.open("%s/view" % self.folder.absolute_url())
        start_app_body_tag = self.browser.contents.index('id="application-body"')
        end_app_body_tag = self.browser.contents.index(">", start_app_body_tag)
        app_body_tag = self.browser.contents[start_app_body_tag:end_app_body_tag]
        self.assertIn(
            "sidebar-left-open",
            app_body_tag,
            msg="Class for open sidebar not found in body tag",
        )

    def test_no_document_title_in_content_area(self):
        self.browser_login_as_site_administrator()
        self.browser.open("%s/view" % self.folder.absolute_url())
        start_document_body = self.browser.contents.index('id="document-body"')
        end_document_body = self.browser.contents.index(
            "</div><!-- #document-body -->", start_document_body
        )
        document_body = self.browser.contents[start_document_body:end_document_body]
        self.assertNotIn(
            "document-title", document_body, msg="Document title found in content area"
        )


class TestBreadcrumbs(BaseViewTest):
    def test_only_workspace_name_in_workspace_view(self):
        view = api.content.get_view(
            context=self.workspace, request=self.request, name="breadcrumbs_view"
        )
        self.assertEqual(
            view.breadcrumbs(),
            (
                {
                    "absolute_url": self.workspace.absolute_url(),
                    "Title": self.workspace.Title(),
                },
            ),
        )

    def test_only_workspace_name_in_document_view(self):
        document = api.content.create(
            container=self.workspace, type="Document", title="A Content Document"
        )
        view = api.content.get_view(
            context=document, request=self.request, name="breadcrumbs_view"
        )
        self.assertEqual(
            view.breadcrumbs(),
            (
                {
                    "absolute_url": self.workspace.absolute_url(),
                    "Title": self.workspace.Title(),
                },
            ),
        )
