import re
from datetime import datetime
from typing import Dict

from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time

from vbv_lernwelt.assignment.models import AssignmentType
from vbv_lernwelt.core.constants import TEST_COURSE_SESSION_BERN_ID
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.course_session.models import (
    CourseSessionAssignment,
    CourseSessionEdoniqTest,
)
from vbv_lernwelt.learnpath.models import (
    LearningContentAssignment,
    LearningContentEdoniqTest,
)
from vbv_lernwelt.notify.email.email_services import EmailTemplate
from vbv_lernwelt.notify.email.reminders.assigment import (
    send_assignment_reminder_notifications,
)
from vbv_lernwelt.notify.models import Notification

EXPECTED_MEMBER_VERB = "Erinnerung: Bald ist ein Abgabetermin"
EXPECTED_EXPERT_VERB = "Erinnerung: Bald ist ein Bewertungstermin"

RECIPIENT_TRAINER = "test-trainer1@example.com"
RECIPIENT_STUDENTS = [
    "test-student1@example.com",
    "test-student2@example.com",
    "test-student3@example.com",
]

ASSIGNMENT_TYPE_LEARNING_CONTENT_LOOKUP: Dict[AssignmentType, str] = {
    AssignmentType.CONDITION_ACCEPTANCE: "test-lehrgang-lp-circle-fahrzeug-lc-redlichkeitserklärung",
    AssignmentType.PREP_ASSIGNMENT: "test-lehrgang-lp-circle-fahrzeug-lc-fahrzeug-mein-erstes-auto",
    AssignmentType.REFLECTION: "test-lehrgang-lp-circle-fahrzeug-lc-reflexion",
    AssignmentType.CASEWORK: "test-lehrgang-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice",
}


def create_assignment(
    assignment_type: AssignmentType,
    submission_deadline=None,
    evaluation_deadline=None,
):
    assignment = CourseSessionAssignment.objects.create(
        course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
        learning_content=LearningContentAssignment.objects.get(
            slug=ASSIGNMENT_TYPE_LEARNING_CONTENT_LOOKUP[assignment_type]
        ),
    )

    assert (
        AssignmentType(assignment.learning_content.assignment_type) == assignment_type
    )

    if submission_deadline:
        assignment.submission_deadline.start = submission_deadline
        assignment.submission_deadline.end = None
        assignment.submission_deadline.save()

    if evaluation_deadline:
        assignment.evaluation_deadline.start = evaluation_deadline
        assignment.evaluation_deadline.end = None
        assignment.evaluation_deadline.save()

    return assignment


def create_edoniq_test_assignment(deadline_start):
    edoniq_test = CourseSessionEdoniqTest.objects.create(
        course_session=CourseSession.objects.get(
            id=TEST_COURSE_SESSION_BERN_ID,
        ),
        learning_content=LearningContentEdoniqTest.objects.get(
            slug="test-lehrgang-lp-circle-fahrzeug-lc-wissens-und-verständnisfragen"
        ),
    )

    edoniq_test.deadline.start = deadline_start
    edoniq_test.deadline.end = None
    edoniq_test.deadline.save()

    return edoniq_test


class TestAssignmentCourseRemindersTest(TestCase):
    def setUp(self):
        create_default_users()
        create_test_course(with_sessions=True)

        CourseSessionAssignment.objects.all().delete()
        CourseSessionEdoniqTest.objects.all().delete()

        Notification.objects.all().delete()

    def _assert_member_assignment_notifications(
        self, action_object, expected_recipients
    ):
        for expected_recipient in expected_recipients:
            notification = Notification.objects.get(
                recipient__username=expected_recipient
            )
            self.assertEqual(action_object, notification.action_object)
            self.assertEqual("ASSIGNMENT_REMINDER", notification.notification_trigger)
            self.assertEqual("INFORMATION", notification.notification_category)
            self.assertEqual(EXPECTED_MEMBER_VERB, notification.verb)

            template_data = notification.data["template_data"]

            self.assertEqual(
                action_object.learning_content.get_parent_circle().title,
                template_data["circle"],
            )

            self.assertEqual(
                action_object.learning_content.get_frontend_url(),
                notification.target_url,
            )

            match = re.fullmatch(
                r"\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}", template_data["due_date"]
            )

            self.assertIsNotNone(
                match, f"due_date format is incorrect: {template_data['due_date']}"
            )

            email_template = notification.data["email_template"]

            # make sure we have the correct email template
            if type(action_object) == CourseSessionAssignment:
                assignment_type = AssignmentType(
                    action_object.learning_content.assignment_type
                )

                if assignment_type == AssignmentType.CASEWORK:
                    self.assertEqual(
                        EmailTemplate.ASSIGNMENT_REMINDER_CASEWORK_MEMBER.name,
                        email_template,
                    )
                elif assignment_type == AssignmentType.PREP_ASSIGNMENT:
                    self.assertEqual(
                        EmailTemplate.ASSIGNMENT_REMINDER_PREP_ASSIGNMENT_MEMBER.name,
                        email_template,
                    )
            elif type(action_object) == CourseSessionEdoniqTest:
                self.assertEqual(
                    EmailTemplate.ASSIGNMENT_REMINDER_EDONIQ_MEMBER.name,
                    email_template,
                )

    @freeze_time("2023-01-01")
    def test_notification_edoniq(self):
        # GIVEN
        should_be_sent = create_edoniq_test_assignment(
            deadline_start=timezone.make_aware(datetime(2023, 1, 2))
        )

        # ...too early
        create_edoniq_test_assignment(
            deadline_start=timezone.make_aware(datetime(2023, 1, 4))
        )

        # ...too late
        create_edoniq_test_assignment(
            deadline_start=timezone.make_aware(datetime(2022, 1, 1))
        )

        # WHEN
        send_assignment_reminder_notifications()

        # THEN
        self.assertEqual(3, len(Notification.objects.all()))
        self._assert_member_assignment_notifications(
            action_object=should_be_sent,
            expected_recipients=RECIPIENT_STUDENTS,
        )

        with self.assertRaises(Notification.DoesNotExist):
            Notification.objects.get(recipient__username=RECIPIENT_TRAINER)

    @freeze_time("2023-01-01")
    def test_notification_casework_for_members(self):
        # GIVEN
        casework = create_assignment(
            assignment_type=AssignmentType.CASEWORK,
            # has a submission deadline within range -> member notification
            submission_deadline=timezone.make_aware(datetime(2023, 1, 2)),
            # but no evaluation deadline within range -> no expert notification
            evaluation_deadline=timezone.make_aware(datetime(2023, 2, 2)),
        )

        # ...too early
        create_assignment(
            assignment_type=AssignmentType.CASEWORK,
            submission_deadline=timezone.make_aware(datetime(2023, 1, 4)),
            evaluation_deadline=timezone.make_aware(datetime(2023, 2, 2)),
        )

        # ...too late
        create_assignment(
            assignment_type=AssignmentType.CASEWORK,
            submission_deadline=timezone.make_aware(datetime(2022, 1, 1)),
            evaluation_deadline=timezone.make_aware(datetime(2022, 2, 2)),
        )

        # WHEN
        send_assignment_reminder_notifications()

        # THEN
        self.assertEqual(3, len(Notification.objects.all()))
        self._assert_member_assignment_notifications(
            action_object=casework,
            expected_recipients=RECIPIENT_STUDENTS,
        )

        with self.assertRaises(Notification.DoesNotExist):
            Notification.objects.get(recipient__username=RECIPIENT_TRAINER)

    @freeze_time("2023-01-01")
    def test_notification_casework_for_experts(self):
        # GIVEN
        casework = create_assignment(
            assignment_type=AssignmentType.CASEWORK,
            submission_deadline=timezone.make_aware(datetime(2022, 12, 12)),
            evaluation_deadline=timezone.make_aware(datetime(2023, 1, 2)),
        )

        # WHEN
        send_assignment_reminder_notifications()

        # THEN
        self.assertEqual(1, len(Notification.objects.all()))

        notification = Notification.objects.get(recipient__username=RECIPIENT_TRAINER)
        self.assertEqual(casework, notification.action_object)
        self.assertEqual("INFORMATION", notification.notification_category)
        self.assertEqual(EXPECTED_EXPERT_VERB, notification.verb)
        self.assertEqual(
            casework.evaluation_deadline.url_expert, notification.target_url
        )
        self.assertEqual(
            "CASEWORK_EXPERT_EVALUATION_REMINDER", notification.notification_trigger
        )

    @freeze_time("2023-01-01")
    def test_notification_prep_assignment(self):
        # GIVEN
        prep_assignment = create_assignment(
            assignment_type=AssignmentType.PREP_ASSIGNMENT,
            submission_deadline=timezone.make_aware(datetime(2023, 1, 2)),
            evaluation_deadline=None,
        )

        # ...too early
        create_assignment(
            assignment_type=AssignmentType.PREP_ASSIGNMENT,
            submission_deadline=timezone.make_aware(datetime(2023, 1, 4)),
            evaluation_deadline=None,
        )

        # ...too late
        create_assignment(
            assignment_type=AssignmentType.PREP_ASSIGNMENT,
            submission_deadline=timezone.make_aware(datetime(2022, 1, 1)),
            evaluation_deadline=None,
        )

        # WHEN
        send_assignment_reminder_notifications()

        # THEN
        self.assertEqual(3, len(Notification.objects.all()))
        self._assert_member_assignment_notifications(
            action_object=prep_assignment,
            expected_recipients=RECIPIENT_STUDENTS,
        )

        with self.assertRaises(Notification.DoesNotExist):
            Notification.objects.get(recipient__username=RECIPIENT_TRAINER)
