from __future__ import annotations

from typing import TYPE_CHECKING

import structlog
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db.models import Model
from notifications.signals import notify

from vbv_lernwelt.assignment.models import AssignmentType
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.course_session.models import CourseSessionAssignment
from vbv_lernwelt.notify.email.email_services import (
    create_template_data_from_course_session_attendance_course,
    EmailTemplate,
    format_swiss_datetime,
    send_email,
)
from vbv_lernwelt.notify.models import (
    Notification,
    NotificationCategory,
    NotificationTrigger,
)
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback

if TYPE_CHECKING:
    from vbv_lernwelt.assignment.models import AssignmentCompletion
    from vbv_lernwelt.course_session.models import (
        CourseSessionAttendanceCourse,
        CourseSessionEdoniqTest,
    )
    from vbv_lernwelt.feedback.models import FeedbackResponse

logger = structlog.get_logger(__name__)


class NotificationService:
    @classmethod
    def send_assignment_submitted_notification(
        cls, recipient: User, sender: User, assignment_completion: AssignmentCompletion
    ):
        if (
            assignment_completion.assignment.assignment_type
            == AssignmentType.PRAXIS_ASSIGNMENT.value
        ):
            texts = {
                "de": "%(sender)s hat den Praxisauftrag «%(assignment_title)s» abgegeben.",
                "fr": "%(sender)s a soumis l'exercice pratique «%(assignment_title)s».",
                "it": "%(sender)s ha consegnato il lavoro pratico «%(assignment_title)s».",
            }
        # this was the default case before the praxis assignment was introduced
        else:
            texts = {
                "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» abgegeben.",
                "fr": "%(sender)s a soumis l'étude de cas dirigée «%(assignment_title)s».",
                "it": "%(sender)s ha consegnato il caso di studio guidato «%(assignment_title)s».",
            }
        verb = texts.get(recipient.language, "de") % {
            "sender": sender.get_full_name(),
            "assignment_title": assignment_completion.assignment.title,
        }

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_SUBMITTED,
            sender=sender,
            target_url=assignment_completion.get_assignment_evaluation_frontend_url(),
            course_session=assignment_completion.course_session,
            action_object=assignment_completion,
            email_template=EmailTemplate.CASEWORK_SUBMITTED,
        )

    @classmethod
    def send_self_evaluation_feedback_request_feedback_notification(
        cls,
        self_evaluation_feedback: SelfEvaluationFeedback,
    ):
        """Requester -> Provider"""
        requester_user = self_evaluation_feedback.feedback_requester_user
        provider_user = self_evaluation_feedback.feedback_provider_user

        texts = {
            "de": "%(requester)s hat eine Selbsteinschätzung mit dir geteilt",
            "fr": "%(requester)s a partagé une auto-évaluation avec vous",
            "it": "%(requester)s ha condiviso una valutazione personale con te",
        }

        verb = texts.get(provider_user.language, "de") % {
            "requester": requester_user.get_full_name(),
        }

        return cls._send_notification(
            recipient=provider_user,
            verb=verb,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.SELF_EVALUATION_FEEDBACK_REQUESTED,
            sender=requester_user,
            target_url=self_evaluation_feedback.feedback_provider_evaluation_url,
            action_object=self_evaluation_feedback,
            email_template=EmailTemplate.SELF_EVALUATION_FEEDBACK_REQUESTED,
            template_data={
                "mentee_name": requester_user.get_full_name(),
                "mentee_email": requester_user.email,
            },
        )

    @classmethod
    def send_self_evaluation_feedback_received_notification(
        cls,
        self_evaluation_feedback: SelfEvaluationFeedback,
    ):
        """Provider -> Requester"""
        requester_user = self_evaluation_feedback.feedback_requester_user
        provider_user = self_evaluation_feedback.feedback_provider_user

        texts = {
            "de": "%(provider)s hat dir eine Fremdeinschätzung gegeben",
            "fr": "%(provider)s vous a donné une évaluation externe",
            "it": "%(provider)s ti ha dato una valutazione esterna",
        }

        verb = texts.get(requester_user.language, "de") % {
            "provider": provider_user.get_full_name(),
        }

        return cls._send_notification(
            recipient=requester_user,
            verb=verb,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.SELF_EVALUATION_FEEDBACK_PROVIDED,
            sender=provider_user,
            target_url=self_evaluation_feedback.feedback_requester_results_url,
            action_object=self_evaluation_feedback,
            email_template=EmailTemplate.SELF_EVALUATION_FEEDBACK_PROVIDED,
            template_data={
                "mentor_name": provider_user.get_full_name(),
                "mentor_email": provider_user.email,
            },
        )

    @classmethod
    def send_assignment_evaluated_notification(
        cls,
        recipient: User,
        sender: User,
        assignment_completion: AssignmentCompletion,
        target_url: str,
    ):
        if (
            assignment_completion.assignment.assignment_type
            == AssignmentType.PRAXIS_ASSIGNMENT.value
        ):
            texts = {
                "de": "%(sender)s hat den Praxisauftrag «%(assignment_title)s» bewertet.",
                "fr": "%(sender)s a évalué l'exercice pratique «%(assignment_title)s».",
                "it": "%(sender)s ha valutato il lavoro pratico «%(assignment_title)s».",
            }
        # this was the default case before the praxis assignment was introduced
        else:
            texts = {
                "de": "%(sender)s hat die geleitete Fallarbeit «%(assignment_title)s» bewertet.",
                "fr": "%(sender)s a évalué l'étude de cas dirigée «%(assignment_title)s».",
                "it": "%(sender)s ha valutato il caso di studio guidato «%(assignment_title)s».",
            }
        verb = texts.get(recipient.language, "de") % {
            "sender": sender.get_full_name(),
            "assignment_title": assignment_completion.assignment.title,
        }

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.USER_INTERACTION,
            notification_trigger=NotificationTrigger.CASEWORK_EVALUATED,
            sender=sender,
            target_url=target_url,
            course_session=assignment_completion.course_session,
            action_object=assignment_completion,
            email_template=EmailTemplate.CASEWORK_EVALUATED,
        )

    @classmethod
    def send_new_feedback_notification(
        cls,
        recipient: User,
        feedback_response: FeedbackResponse,
    ):
        texts = {
            "de": "Feedback abgeschickt für Circle «%(circle_title)s»",
            "fr": "Feedback envoyé pour le cercle «%(circle_title)s»",
            "it": "Feedback inviato per il cerchio «%(circle_title)s»",
        }
        verb = texts.get(recipient.language, "de") % {
            "circle_title": feedback_response.circle.title,
        }

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.INFORMATION,
            notification_trigger=NotificationTrigger.NEW_FEEDBACK,
            target_url=f"/course/{feedback_response.course_session.course.slug}/cockpit/feedback/{feedback_response.circle_id}/",
            course_session=feedback_response.course_session,
            action_object=feedback_response,
            email_template=EmailTemplate.NEW_FEEDBACK,
        )

    @classmethod
    def send_attendance_course_reminder_notification(
        cls,
        recipient: User,
        attendance_course: CourseSessionAttendanceCourse,
    ):
        texts = {
            "de": "Erinnerung: Bald findet ein Präsenzkurs statt",
            "fr": "Rappel: Un cours de présence aura lieu bientôt.",
            "it": "Promemoria: Un corso di presenza avrà luogo presto.",
        }
        verb = texts.get(recipient.language, "de")

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.INFORMATION,
            notification_trigger=NotificationTrigger.ATTENDANCE_COURSE_REMINDER,
            target_url=attendance_course.learning_content.get_frontend_url(),
            action_object=attendance_course,
            course_session=attendance_course.course_session,
            email_template=EmailTemplate.ATTENDANCE_COURSE_REMINDER,
            template_data=create_template_data_from_course_session_attendance_course(
                attendance_course=attendance_course
            ),
        )

    @classmethod
    def send_assignment_reminder_notification_member(
        cls,
        recipient: User,
        assignment: CourseSessionAssignment,
    ):
        texts = {
            "de": "Erinnerung: Bald ist ein Abgabetermin",
            "fr": "Rappel: Une date limite approche",
            "it": "Promemoria: Una scadenza si avvicina",
        }

        templates = {
            AssignmentType.CASEWORK: EmailTemplate.ASSIGNMENT_REMINDER_CASEWORK_MEMBER,
            AssignmentType.PREP_ASSIGNMENT: EmailTemplate.ASSIGNMENT_REMINDER_PREP_ASSIGNMENT_MEMBER,
        }

        verb = texts.get(recipient.language, "de")
        circle = assignment.learning_content.get_parent_circle().title
        due_at = assignment.submission_deadline.start

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.INFORMATION,
            notification_trigger=NotificationTrigger.ASSIGNMENT_REMINDER,
            target_url=assignment.learning_content.get_frontend_url(),
            action_object=assignment,
            course_session=assignment.course_session,
            email_template=templates[
                AssignmentType(assignment.learning_content.assignment_type)
            ],
            template_data={
                "circle": circle,
                "due_date": format_swiss_datetime(due_at),
            },
        )

    @classmethod
    def send_edoniq_test_reminder_notification_member(
        cls,
        recipient: User,
        edoniq_test: CourseSessionEdoniqTest,
    ):
        texts = {
            "de": "Erinnerung: Bald ist ein Abgabetermin",
            "fr": "Rappel: Une date limite approche",
            "it": "Promemoria: Una scadenza si avvicina",
        }

        verb = texts.get(recipient.language, "de")
        circle = edoniq_test.learning_content.get_parent_circle().title
        due_at = edoniq_test.deadline.start

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.INFORMATION,
            notification_trigger=NotificationTrigger.ASSIGNMENT_REMINDER,
            target_url=edoniq_test.learning_content.get_frontend_url(),
            action_object=edoniq_test,
            course_session=edoniq_test.course_session,
            email_template=EmailTemplate.ASSIGNMENT_REMINDER_EDONIQ_MEMBER,
            template_data={
                "circle": circle,
                "due_date": format_swiss_datetime(due_at),
            },
        )

    @classmethod
    def send_casework_expert_evaluation_reminder(
        cls,
        recipient: User,
        assignment: CourseSessionAssignment,
    ):
        texts = {
            "de": "Erinnerung: Bald ist ein Bewertungstermin",
            "fr": "Rappel: Une date limite approche",
            "it": "Promemoria: Una scadenza si avvicina",
        }

        verb = texts.get(recipient.language, "de")
        circle = assignment.learning_content.get_parent_circle().title
        due_at = assignment.evaluation_deadline.start

        return cls._send_notification(
            recipient=recipient,
            verb=verb,
            notification_category=NotificationCategory.INFORMATION,
            notification_trigger=NotificationTrigger.CASEWORK_EXPERT_EVALUATION_REMINDER,
            target_url=assignment.evaluation_deadline.url_expert,
            action_object=assignment,
            course_session=assignment.course_session,
            email_template=EmailTemplate.EVALUATION_REMINDER_CASEWORK_EXPERT,
            template_data={
                "circle": circle,
                "due_date": format_swiss_datetime(due_at),
            },
        )

    @classmethod
    def _send_notification(
        cls,
        recipient: User,
        verb: str,
        notification_category: NotificationCategory,
        notification_trigger: NotificationTrigger,
        sender: User | None = None,
        action_object: Model | None = None,
        target_url: str | None = None,
        course_session: CourseSession | None = None,
        email_template: EmailTemplate | None = None,
        template_data: dict | None = None,
        fail_silently: bool = True,
    ) -> str:
        if template_data is None:
            template_data = {}

        notification_identifier = (
            f"{notification_category.name}_{notification_trigger.name}"
        )

        actor_avatar_url = ""
        if not sender:
            sender = User.objects.get(email="admin")
        else:
            actor_avatar_url = sender.avatar_url
        log = logger.bind(
            recipient=recipient.email,
            sender=sender.email,
            verb=verb,
            notification_category=notification_category,
            notification_trigger=notification_trigger,
            course_session=course_session.title if course_session else "",
            target_url=target_url,
            template_data=template_data,
        )
        emailed = False

        try:
            validate_email(recipient.email)
        except ValidationError:
            log.info("Recipient email is invalid")
            return f"{notification_identifier}_invalid_email"

        try:
            notification = NotificationService._find_duplicate_notification(
                recipient=recipient,
                verb=verb,
                notification_category=notification_category,
                notification_trigger=notification_trigger,
                target_url=target_url,
                course_session=course_session,
            )
            emailed = False
            if notification and notification.emailed:
                emailed = True

            if (
                email_template
                and cls._should_send_email(notification_category, recipient)
                and not emailed
            ):
                log.debug("Try to send email")
                try:
                    emailed = cls._send_email(
                        recipient=recipient,
                        template=email_template,
                        template_data={
                            "target_url": f"https://my.vbv-afa.ch{target_url}",
                            **template_data,
                        },
                        fail_silently=False,
                    )
                except Exception as e:
                    notification_identifier += "_email_error"
                    if not fail_silently:
                        raise e
                    return notification_identifier

                if emailed:
                    notification_identifier += "_emailed"
                    if notification:
                        notification.emailed = True
                        notification.save()
            else:
                log.debug("Should not send email")

            if notification:
                log.info("Duplicate notification was omitted from being sent")
                notification_identifier += "_duplicate"
                return notification_identifier

            else:
                response = notify.send(
                    sender=sender,
                    recipient=recipient,
                    verb=verb,
                    action_object=action_object,
                    emailed=emailed,
                    # The extra arguments are saved in the 'data' member
                    email_template=email_template.name if email_template else "",
                    template_data=template_data,
                )

                sent_notification: Notification = response[0][1][0]  # 🫨
                sent_notification.target_url = target_url
                sent_notification.notification_category = notification_category
                sent_notification.notification_trigger = notification_trigger
                sent_notification.course_session = course_session
                sent_notification.actor_avatar_url = actor_avatar_url
                sent_notification.save()
                log.info("Notification sent successfully", emailed=emailed)
            return f"{notification_identifier}_success"
        except Exception as e:
            log.error(
                "Failed to send notification",
                exception=str(e),
                exc_info=True,
                stack_info=True,
                emailed=emailed,
            )
            if not fail_silently:
                raise e
            return f"{notification_identifier}_error"

    @staticmethod
    def _should_send_email(
        notification_category: NotificationCategory, recipient: User
    ) -> bool:
        return str(notification_category) in recipient.additional_json_data.get(
            "email_notification_categories", []
        )

    @staticmethod
    def _send_email(
        recipient: User,
        template: EmailTemplate,
        template_data: dict,
        fail_silently: bool = True,
    ) -> bool:
        return send_email(
            recipient_email=recipient.email,
            template=template,
            template_data=template_data,
            template_language=recipient.language,
            fail_silently=fail_silently,
        )

    @staticmethod
    def _find_duplicate_notification(
        recipient: User,
        verb: str,
        notification_category: NotificationCategory,
        notification_trigger: NotificationTrigger,
        target_url: str | None,
        course_session: CourseSession | None,
    ) -> Notification | None:
        """Check if a notification with the same parameters has already been sent to the recipient.
        This is to prevent duplicate notifications from being sent and to protect against potential programming errors.
        """
        return Notification.objects.filter(
            recipient=recipient,
            verb=verb,
            notification_category=notification_category,
            notification_trigger=notification_trigger,
            target_url=target_url,
            course_session=course_session,
        ).first()
