from plone import api
from plone.app.testing import helpers
from plone.app.testing import IntegrationTesting
from plone.app.testing import PloneSandboxLayer
from ploneintranet import api as pi_api
from ploneintranet.notifications.sql.saconfig import LoggedEvent
from ploneintranet.notifications.sql.saconfig import Notification
from ploneintranet.notifications.sql.saconfig import session
from ploneintranet.notifications.testing import SQL_FIXTURE
from ploneintranet.todo.testing import INTEGRATION_TESTING
from zope.event import notify
from zope.i18nmessageid import Message
from zope.lifecycleevent import ObjectModifiedEvent

import unittest


class PloneintranetTodoNotificationsLayer(PloneSandboxLayer):
    defaultBases = (SQL_FIXTURE, INTEGRATION_TESTING)

    def setUpZope(self, app, configurationContext):
        import ploneintranet.notifications.tests

        self.loadZCML(package=ploneintranet.notifications.tests)

    def setUpPloneSite(self, portal):
        helpers.applyProfile(portal, "ploneintranet.notifications.tests:testing")
        pi_api.userprofile.create(
            "alice-actor",
            email="actor@example.org",
            properties={"first_name": "Alice", "last_name": "Actor"},
            approve=True,
        )
        pi_api.userprofile.create(
            "arthur-assignee",
            email="assignee@example.org",
            properties={"first_name": "Arthur", "last_name": "Assignee"},
            approve=True,
        )


FIXTURE = PloneintranetTodoNotificationsLayer()
LAYER = IntegrationTesting(
    bases=(FIXTURE,), name="PloneintranetTodoNotificationsLayer:Integration"
)


class TestTodoNotificationEmail(unittest.TestCase):
    layer = LAYER

    def setUp(self):
        super().setUp()
        self.portal = self.layer["portal"]
        self.request = self.layer["request"]
        self.actor = self.portal["profiles"]["alice-actor"]
        self.assignee = self.portal["profiles"]["arthur-assignee"]
        self.mailhost = self.portal.MailHost

    def assertUserReceivesNotificationsOnChannels(self, user_id, channels):
        """Check that the user has the right number of notifications"""
        notifications = session.query(Notification).filter_by(recipient_id=user_id)
        self.assertSetEqual(
            {notification.recipient_id for notification in notifications},
            {user_id},
        )
        self.assertEqual(notifications.count(), len(channels))
        self.assertSetEqual(
            {notification.channel for notification in notifications},
            channels,
        )

    def assertUserGetsNotificationText(self, user_id, text):
        """Check that the user gets the right notification text"""
        notifications = session.query(Notification).filter_by(recipient_id=user_id)
        notification = notifications.first()

        self.assertEqual(len(notification.logged_event.category_provider.categories), 1)
        category = notification.logged_event.category_provider.categories[0]
        expected = category.get_message_for(notification)
        if isinstance(expected, Message):
            expected = api.portal.translate(expected)
        return self.assertEqual(text, expected)

    def test_email_on_object_added(self):
        # Check the emails that are sent out after creating a todo with an assignee
        request = self.request
        request.form["title"] = "A task"
        request.form["portal_type"] = "todo"
        request.form["assignee"] = self.assignee.getId()

        with api.env.adopt_user(self.actor.getId()):
            api.content.get_view("add_task", self.actor, request)()
            logged_event = session.query(LoggedEvent).one()
            self.assertEqual(logged_event.actor_id, self.actor.getId())

        # Dispatch the notifications
        logged_event.notify_all()

        # Arthur gets three notifications: desktop, email and record
        self.assertUserReceivesNotificationsOnChannels(
            "arthur-assignee", {"desktop", "email", "record"}
        )

        # Check the notification text
        self.assertUserGetsNotificationText(
            "arthur-assignee",
            "Alice Actor assigned a task to you in Personal task list: A task.",
        )

    def test_email_on_object_modified(self):
        todo = api.content.create(
            type="todo",
            title="todo1",
            container=self.portal,
            assignee=self.assignee.getId(),
        )
        with api.env.adopt_user(self.actor.getId()):
            notify(ObjectModifiedEvent(todo))
            logged_event = session.query(LoggedEvent).one()
            self.assertEqual(logged_event.actor_id, self.actor.getId())

        # Dispatch the notifications
        logged_event.notify_all()

        # Arthur gets three notifications: desktop, email and record
        self.assertUserReceivesNotificationsOnChannels(
            "arthur-assignee", {"desktop", "email", "record"}
        )

        # Check the notification text
        self.assertUserGetsNotificationText(
            "arthur-assignee",
            "Alice Actor modified your task in Personal task list: todo1.",
        )

    def test_email_on_transitions(self):
        initiator = pi_api.userprofile.create(
            "igor-initiator",
            email="initiator@example.org",
            properties={"fullname": "Igor Initiator"},
            approve=True,
        )

        with api.env.adopt_user(self.actor.getId()):
            todo = api.content.create(
                type="todo",
                title="todo1",
                container=self.actor,
                assignee=self.assignee.getId(),
                initiator=initiator.getId(),
            )

        # A mail to the assignee and the initiator is sent when we close a task
        with api.env.adopt_user(self.actor.getId()):
            api.content.transition(obj=todo, to_state="done")
            logged_events = session.query(LoggedEvent)
            self.assertEqual(logged_events.count(), 2)
            self.assertSetEqual(
                {logged_event.actor_id for logged_event in logged_events},
                {self.actor.getId()},
            )

        # Dispatch the notifications
        for logged_event in logged_events:
            logged_event.notify_all()

        # Arthur and Igor gets three notifications: desktop, email and record
        self.assertUserReceivesNotificationsOnChannels(
            self.assignee.getId(), {"desktop", "email", "record"}
        )
        self.assertUserReceivesNotificationsOnChannels(
            initiator.getId(), {"desktop", "email", "record"}
        )

        self.assertUserGetsNotificationText(
            self.assignee.getId(),
            "Alice Actor closed your task in Personal task list: todo1.",
        )
        self.assertUserGetsNotificationText(
            initiator.getId(),
            "Alice Actor closed a task in Personal task list: todo1.",
        )

        # Clear the tables
        session.query(Notification).delete()
        session.query(LoggedEvent).delete()
        session.flush()

        # No new notification will occur when we set the state to planned
        with api.env.adopt_user(self.actor.getId()):
            api.content.transition(obj=todo, to_state="planned")
            self.assertEqual(session.query(LoggedEvent).count(), 0)

        # A mail to the assignee and the initiator is sent when we open a task
        with api.env.adopt_user(self.actor.getId()):
            api.content.transition(obj=todo, to_state="open")
            self.assertEqual(session.query(LoggedEvent).count(), 2)
            logged_events = session.query(LoggedEvent)
            self.assertEqual(logged_events.count(), 2)
            self.assertSetEqual(
                {logged_event.actor_id for logged_event in logged_events},
                {self.actor.getId()},
            )

        # Dispatch the notifications
        for logged_event in logged_events:
            logged_event.notify_all()

        # Arthur and Igor gets three notifications: desktop, email and record
        self.assertUserReceivesNotificationsOnChannels(
            self.assignee.getId(), {"desktop", "email", "record"}
        )
        self.assertUserReceivesNotificationsOnChannels(
            initiator.getId(), {"desktop", "email", "record"}
        )

        self.assertUserGetsNotificationText(
            self.assignee.getId(),
            "Alice Actor assigned a task to you in Personal task list: todo1.",
        )

        self.assertUserGetsNotificationText(
            initiator.getId(),
            "Alice Actor modified a task in Personal task list: todo1.",
        )
