"""email tests"""

from smtplib import SMTPException
from unittest.mock import MagicMock, PropertyMock, patch

from django.core import mail
from django.core.mail.backends.locmem import EmailBackend
from django.urls import reverse

from authentik.core.models import User
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user
from authentik.events.models import Event, EventAction
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.stages.email.models import EmailStage


class TestEmailStageSending(FlowTestCase):
    """Email tests"""

    def setUp(self):
        super().setUp()
        self.user = create_test_admin_user()

        self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
        self.stage = EmailStage.objects.create(
            name="email",
        )
        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)

    def test_pending_user(self):
        """Test with pending user"""
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
        plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()
        Event.objects.filter(action=EventAction.EMAIL_SENT).delete()

        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
        with patch(
            "authentik.stages.email.models.EmailStage.backend_class",
            PropertyMock(return_value=EmailBackend),
        ):
            response = self.client.post(url)
            self.assertEqual(response.status_code, 200)
            self.assertStageResponse(
                response,
                self.flow,
                response_errors={
                    "non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
                },
            )
            self.assertEqual(len(mail.outbox), 1)
            self.assertEqual(mail.outbox[0].subject, "authentik")
            events = Event.objects.filter(action=EventAction.EMAIL_SENT)
            self.assertEqual(len(events), 1)
            event = events.first()
            self.assertEqual(
                event.context["message"], f"Email to {self.user.name} <{self.user.email}> sent"
            )
            self.assertEqual(event.context["subject"], "authentik")
            self.assertEqual(event.context["to_email"], [f"{self.user.name} <{self.user.email}>"])
            self.assertEqual(event.context["from_email"], "system@authentik.local")

    def test_newlines_long_name(self):
        """Test with pending user"""
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
        long_user = create_test_user()
        long_user.name = "Test User\r\n Many Words\r\n"
        long_user.save()
        plan.context[PLAN_CONTEXT_PENDING_USER] = long_user
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()
        Event.objects.filter(action=EventAction.EMAIL_SENT).delete()

        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
        with patch(
            "authentik.stages.email.models.EmailStage.backend_class",
            PropertyMock(return_value=EmailBackend),
        ):
            response = self.client.post(url)
            self.assertEqual(response.status_code, 200)
            self.assertStageResponse(
                response,
                self.flow,
                response_errors={
                    "non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
                },
            )
            self.assertEqual(len(mail.outbox), 1)
            self.assertEqual(mail.outbox[0].subject, "authentik")
            self.assertEqual(mail.outbox[0].to, [f"Test User   Many Words   <{long_user.email}>"])

    def test_utf8_name(self):
        """Test with pending user"""
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
        utf8_user = create_test_user()
        utf8_user.name = "Cirilo ЉМНЊ el cirilico И̂ӢЙӤ "
        utf8_user.email = "cyrillic@authentik.local"
        utf8_user.save()
        plan.context[PLAN_CONTEXT_PENDING_USER] = utf8_user
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()
        Event.objects.filter(action=EventAction.EMAIL_SENT).delete()

        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
        with patch(
            "authentik.stages.email.models.EmailStage.backend_class",
            PropertyMock(return_value=EmailBackend),
        ):
            response = self.client.post(url)
            self.assertEqual(response.status_code, 200)
            self.assertStageResponse(
                response,
                self.flow,
                response_errors={
                    "non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
                },
            )
            self.assertEqual(len(mail.outbox), 1)
            self.assertEqual(mail.outbox[0].subject, "authentik")
            self.assertEqual(mail.outbox[0].to, [f"{utf8_user.email}"])

    def test_pending_fake_user(self):
        """Test with pending (fake) user"""
        self.flow.designation = FlowDesignation.RECOVERY
        self.flow.save()
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
        plan.context[PLAN_CONTEXT_PENDING_USER] = User(username=generate_id())
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()

        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
        with patch(
            "authentik.stages.email.models.EmailStage.backend_class",
            PropertyMock(return_value=EmailBackend),
        ):
            response = self.client.post(url)
            self.assertEqual(response.status_code, 200)
            self.assertStageResponse(
                response,
                self.flow,
                response_errors={
                    "non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
                },
            )
            self.assertEqual(len(mail.outbox), 0)

    def test_send_error(self):
        """Test error during sending (sending will be retried)"""
        plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
        plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
        session = self.client.session
        session[SESSION_KEY_PLAN] = plan
        session.save()

        url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
        with patch(
            "authentik.stages.email.models.EmailStage.backend_class",
            PropertyMock(return_value=EmailBackend),
        ):
            with patch(
                "django.core.mail.backends.locmem.EmailBackend.send_messages",
                MagicMock(side_effect=[SMTPException, EmailBackend.send_messages]),
            ):
                response = self.client.post(url)
            response = self.client.post(url)
            self.assertEqual(response.status_code, 200)
            self.assertGreaterEqual(len(mail.outbox), 1)
            self.assertEqual(mail.outbox[0].subject, "authentik")
