import json

from django.test import TestCase

from vbv_lernwelt.core.models import User
from vbv_lernwelt.core.tests.factories import UserFactory
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.models import (
    Notification,
    NotificationCategory,
    NotificationTrigger,
)
from vbv_lernwelt.notify.services import NotificationService


class TestNotificationService(TestCase):
    def _on_send_email(self, *_, **__) -> bool:
        self._emails_sent += 1
        return True

    def _has_sent_emails(self) -> bool:
        return self._emails_sent != 0

    def setUp(self) -> None:
        self._emails_sent = 0
        self.notification_service = NotificationService
        self.notification_service._send_email = self._on_send_email

        self.admin = UserFactory(username="admin", email="admin")
        self.sender_username = "Bob"
        UserFactory(username=self.sender_username, email="bob@example.com")
        self.sender = User.objects.get(username=self.sender_username)

        self.recipient_username = "Alice"
        UserFactory(username=self.recipient_username, email="alice@example.com")
        self.recipient = User.objects.get(username=self.recipient_username)
        self.recipient.save()

        self.client.login(username=self.recipient, password="pw")

    def test_send_notification_without_email(self):
        verb = "Anne hat deinen Auftrag bewertet"
        target_url = "https://www.vbv.ch"
        result = self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb=verb,
            target_url=target_url,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            fail_silently=False,
        )

        self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_success")

        self.assertEqual(1, Notification.objects.count())
        notification: Notification = Notification.objects.first()
        self.assertEqual(self.sender, notification.actor)
        self.assertEqual(verb, notification.verb)
        self.assertEqual(target_url, notification.target_url)
        self.assertEqual(
            str(NotificationCategory.USER_INTERACTION),
            notification.notification_category,
        )
        self.assertFalse(notification.emailed)

    def test_send_notification_with_email(self):
        self.recipient.additional_json_data[
            "email_notification_categories"
        ] = json.dumps(["USER_INTERACTION"])
        self.recipient.save()

        verb = "Anne hat deinen Auftrag bewertet"
        target_url = "https://www.vbv.ch"
        result = self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb=verb,
            target_url=target_url,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            fail_silently=False,
        )

        self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_emailed_success")

        self.assertEqual(1, Notification.objects.count())
        notification: Notification = Notification.objects.first()
        self.assertEqual(self.sender, notification.actor)
        self.assertEqual(verb, notification.verb)
        self.assertEqual(target_url, notification.target_url)
        self.assertEqual(
            str(NotificationCategory.USER_INTERACTION),
            notification.notification_category,
        )
        self.assertTrue(notification.emailed)

    def test_does_not_send_duplicate_notification(self):
        verb = "Anne hat deinen Auftrag bewertet"
        target_url = "https://www.vbv.ch"

        result = self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb=verb,
            target_url=target_url,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            template_data={
                "blah": 123,
                "foo": "ich habe hunger",
            },
        )
        self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_success")

        result = self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb=verb,
            target_url=target_url,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            template_data={
                "blah": 123,
                "foo": "ich habe hunger",
            },
        )
        self.assertEqual(result, "USER_INTERACTION_CASEWORK_SUBMITTED_duplicate")

        self.assertEqual(1, Notification.objects.count())
        notification: Notification = Notification.objects.first()
        self.assertEqual(self.sender, notification.actor)
        self.assertEqual(verb, notification.verb)
        self.assertEqual(target_url, notification.target_url)
        self.assertEqual(
            str(NotificationCategory.USER_INTERACTION),
            notification.notification_category,
        )
        self.assertEqual(
            str(NotificationTrigger.CASEWORK_SUBMITTED),
            notification.notification_trigger,
        )
        self.assertFalse(notification.emailed)

        # when the email was not sent, yet it will still send it afterwards...
        self.recipient.additional_json_data[
            "email_notification_categories"
        ] = json.dumps(["USER_INTERACTION"])
        self.recipient.save()

        result = self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb=verb,
            target_url=target_url,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            template_data={
                "blah": 123,
                "foo": "ich habe hunger",
            },
        )
        self.assertEqual(
            result, "USER_INTERACTION_CASEWORK_SUBMITTED_emailed_duplicate"
        )

        self.assertEqual(1, Notification.objects.count())
        notification: Notification = Notification.objects.first()
        self.assertTrue(notification.emailed)

    def test_only_sends_email_if_enabled(self):
        # Assert no mail is sent if corresponding email notification type is not enabled
        self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb="should not be sent",
            target_url="",
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            template_data={},
        )
        self.assertEqual(1, Notification.objects.count())
        self.assertFalse(self._has_sent_emails())

        # Assert mail is sent if corresponding email notification type is enabled
        self.recipient.additional_json_data[
            "email_notification_categories"
        ] = json.dumps(["USER_INTERACTION"])
        self.recipient.save()
        self.notification_service._send_notification(
            sender=self.sender,
            recipient=self.recipient,
            verb="should be sent",
            target_url="",
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
            template_data={},
        )
        self.assertEqual(2, Notification.objects.count())
        self.assertTrue(self._has_sent_emails())
