from unittest.mock import patch

from django.urls import reverse
from rest_framework.test import APITestCase

from vbv_lernwelt.course.creators.test_utils import (
    add_course_session_user,
    create_circle,
    create_course,
    create_course_session,
    create_learning_unit,
    create_performance_criteria_page,
    create_user,
)
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSessionUser
from vbv_lernwelt.course.services import mark_course_completion
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import LearningUnitPerformanceFeedbackType
from vbv_lernwelt.self_evaluation_feedback.models import (
    CourseCompletionFeedback,
    SelfEvaluationFeedback,
)


def create_self_evaluation_feedback(
    learning_unit, feedback_requester_user, feedback_provider_user
):
    return SelfEvaluationFeedback.objects.create(
        learning_unit=learning_unit,
        feedback_requester_user=feedback_requester_user,
        feedback_provider_user=feedback_provider_user,
    )


class SelfEvaluationFeedbackAPI(APITestCase):
    def setUp(self) -> None:
        self.member = create_user("member")
        self.mentor = create_user("mentor")

        self.course, self.course_page = create_course("Test Course")
        self.course_session = create_course_session(
            course=self.course, title="Test Bern 2022 a"
        )

        member_csu = add_course_session_user(
            course_session=self.course_session,
            user=self.member,
            role=CourseSessionUser.Role.MEMBER,
        )

        self.circle, _ = create_circle(
            title="Test Circle", course_page=self.course_page
        )

        learning_mentor = LearningMentor.objects.create(
            mentor=self.mentor,
            course=self.course_session.course,
        )

        learning_mentor.participants.add(member_csu)

    @patch(
        "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_request_feedback_notification"
    )
    def test_start_self_evaluation_feedback(self, mock_notification_service_send):
        # GIVEN
        learning_unit = create_learning_unit(course=self.course, circle=self.circle)

        pc = create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        mark_course_completion(
            page=pc,
            user=self.member,
            course_session=self.course_session,
            completion_status="SUCCESS",
        )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.post(
            reverse(
                "start_self_evaluation_feedback",
                args=[learning_unit.id],
            ),
            {
                "feedback_provider_user_id": self.mentor.id,
            },
        )

        # make sure re-starting is a no-op
        self.client.post(
            reverse(
                "start_self_evaluation_feedback",
                args=[learning_unit.id],
            ),
            {
                "feedback_provider_user_id": self.mentor.id,
            },
        )

        # shall be idempotent
        self.client.post(
            reverse(
                "start_self_evaluation_feedback",
                args=[learning_unit.id],
            ),
            {
                "feedback_provider_user_id": self.mentor.id,
            },
        )

        # THEN
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data["success"], True)

        self.assertEqual(
            SelfEvaluationFeedback.objects.count(),
            1,
        )

        self_evaluation_feedback = SelfEvaluationFeedback.objects.first()
        self.assertEqual(self_evaluation_feedback.feedback_requester_user, self.member)
        self.assertEqual(self_evaluation_feedback.feedback_provider_user, self.mentor)
        self.assertEqual(self_evaluation_feedback.learning_unit, learning_unit)

        mock_notification_service_send.assert_called_once_with(
            self_evaluation_feedback=self_evaluation_feedback
        )

    def test_start_self_evaluation_feedback_not_allowed_user(self):
        # GIVEN
        learning_unit = create_learning_unit(course=self.course, circle=self.circle)
        not_a_mentor = create_user("not_a_mentor")

        self.client.force_login(self.member)

        # WHEN
        response = self.client.post(
            reverse("start_self_evaluation_feedback", args=[learning_unit.id]),
            {
                "feedback_provider_user_id": not_a_mentor.id,
            },
        )

        # THEN
        self.assertEqual(response.status_code, 403)

    def test_get_self_evaluation_feedback_as_requester(self):
        """Tests endpoint of feedback REQUESTER"""

        # GIVEN
        learning_unit = create_learning_unit(  # noqa
            course=self.course, circle=self.circle
        )

        performance_criteria_1 = create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        completion = mark_course_completion(
            page=performance_criteria_1,
            user=self.member,
            course_session=self.course_session,
            completion_status=CourseCompletionStatus.SUCCESS.value,
        )

        self_evaluation_feedback = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        CourseCompletionFeedback.objects.create(
            feedback=self_evaluation_feedback,
            course_completion=completion,
            feedback_assessment=CourseCompletionStatus.FAIL.value,
        )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedback_as_requester",
                args=[learning_unit.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        feedback = response.data
        self.assertEqual(feedback["learning_unit_id"], learning_unit.id)
        self.assertFalse(feedback["feedback_submitted"])
        self.assertEqual(feedback["circle_name"], self.circle.title)  # noqa

        provider_user = feedback["feedback_provider_user"]
        self.assertEqual(provider_user["id"], str(self.mentor.id))
        self.assertEqual(provider_user["first_name"], self.mentor.first_name)
        self.assertEqual(provider_user["last_name"], self.mentor.last_name)
        self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url)

        requester_user = feedback["feedback_requester_user"]
        self.assertEqual(requester_user["id"], str(self.member.id))  # noqa
        self.assertEqual(requester_user["first_name"], self.member.first_name)
        self.assertEqual(requester_user["last_name"], self.member.last_name)
        self.assertEqual(requester_user["avatar_url"], self.member.avatar_url)

        self.assertEqual(len(feedback["criteria"]), 2)

        first_criteria = feedback["criteria"][0]
        self.assertEqual(first_criteria["course_completion_id"], completion.id)
        self.assertEqual(first_criteria["title"], performance_criteria_1.title)
        self.assertEqual(
            first_criteria["self_assessment"],
            CourseCompletionStatus.SUCCESS.value,
        )
        self.assertEqual(
            first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value
        )

        second_criteria = feedback["criteria"][1]
        self.assertEqual(second_criteria["course_completion_id"], None)
        self.assertEqual(second_criteria["title"], performance_criteria_1.title)
        self.assertEqual(
            second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value
        )
        self.assertEqual(
            second_criteria["feedback_assessment"],
            CourseCompletionStatus.UNKNOWN.value,
        )

    def test_feedbacks_with_mixed_completion_statuses(self):
        """Case: CourseCompletion AND feedbacks with mixed completion statuses"""

        # GIVEN
        learning_unit = create_learning_unit(
            course=self.course,
            circle=self.circle,
            feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
        )

        feedback = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        feedback.feedback_submitted = True
        feedback.save()

        for status in [
            CourseCompletionStatus.SUCCESS,
            CourseCompletionStatus.FAIL,
            CourseCompletionStatus.UNKNOWN,
        ]:
            criteria_page = create_performance_criteria_page(
                course=self.course,
                course_page=self.course_page,
                circle=self.circle,
                learning_unit=learning_unit,
            )

            # self assessment
            completion = mark_course_completion(
                page=criteria_page,
                user=self.member,
                course_session=self.course_session,
                completion_status=status.value,
            )

            # feedback assessment
            CourseCompletionFeedback.objects.create(
                feedback=feedback,
                course_completion=completion,
                feedback_assessment=status.value,
            )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedbacks_as_requester",
                args=[self.course_session.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        result = response.data["results"][0]

        self_assessment = result["self_assessment"]
        self.assertEqual(self_assessment["counts"]["pass"], 1)
        self.assertEqual(self_assessment["counts"]["fail"], 1)
        self.assertEqual(self_assessment["counts"]["unknown"], 1)

        feedback_assessment = result["feedback_assessment"]
        self.assertEqual(feedback_assessment["counts"]["pass"], 1)
        self.assertEqual(feedback_assessment["counts"]["fail"], 1)
        self.assertEqual(feedback_assessment["counts"]["unknown"], 1)

        self.assertTrue(feedback_assessment["submitted_by_provider"])
        self.assertEqual(
            feedback_assessment["provider_user"]["id"], str(self.mentor.id)
        )

        aggregate = response.data["aggregates"]
        self.assertEqual(aggregate["self_assessment"]["pass"], 1)
        self.assertEqual(aggregate["self_assessment"]["fail"], 1)
        self.assertEqual(aggregate["self_assessment"]["unknown"], 1)
        self.assertEqual(aggregate["feedback_assessment"]["pass"], 1)
        self.assertEqual(aggregate["feedback_assessment"]["fail"], 1)
        self.assertEqual(aggregate["feedback_assessment"]["unknown"], 1)

    def test_no_feedbacks_but_with_completion_status(self):
        """Case: CourseCompletion but NO feedback"""

        # GIVEN
        learning_unit_with_success_feedback = create_learning_unit(
            course=self.course,
            circle=self.circle,
            feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
        )

        performance_criteria_page = create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit_with_success_feedback,
        )

        # IMPORTANT: CourseCompletion but NO feedback!

        mark_course_completion(
            page=performance_criteria_page,
            user=self.member,
            course_session=self.course_session,
            completion_status=CourseCompletionStatus.SUCCESS.value,
        )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedbacks_as_requester",
                args=[self.course_session.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        result = response.data["results"][0]
        counts = result["self_assessment"]["counts"]
        self.assertEqual(counts["pass"], 1)
        self.assertEqual(counts["fail"], 0)
        self.assertEqual(counts["unknown"], 0)

    def test_feedbacks_not_started(self):
        """Case: Learning unit with no completion status and no feedback"""

        # GIVEN
        learning_unit = create_learning_unit(  # noqa
            course=self.course,
            circle=self.circle,
            feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
        )

        create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedbacks_as_requester",
                args=[self.course_session.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        result = response.data["results"][0]
        self.assertEqual(result["self_assessment"]["counts"]["pass"], 0)
        self.assertEqual(result["self_assessment"]["counts"]["fail"], 0)
        self.assertEqual(result["self_assessment"]["counts"]["unknown"], 1)

    def test_feedbacks_metadata(self):
        # GIVEN
        learning_unit = create_learning_unit(  # noqa
            course=self.course,
            circle=self.circle,
            feedback_user=LearningUnitPerformanceFeedbackType.MENTOR_FEEDBACK,
        )

        create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        self.client.force_login(self.member)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedbacks_as_requester",
                args=[self.course_session.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        result = response.data["results"][0]
        self.assertEqual(result["title"], learning_unit.title)
        self.assertEqual(result["id"], learning_unit.id)
        self.assertEqual(result["circle_id"], self.circle.id)
        self.assertEqual(result["circle_title"], self.circle.title)
        self.assertEqual(result["detail_url"], learning_unit.get_evaluate_url())

        circles = response.data["circles"]
        self.assertEqual(len(circles), 1)
        self.assertEqual(circles[0]["id"], self.circle.id)
        self.assertEqual(circles[0]["title"], self.circle.title)

    def test_get_self_evaluation_feedback_as_provider(self):
        """Tests endpoint of feedback PROVIDER"""

        # GIVEN
        learning_unit = create_learning_unit(  # noqa
            course=self.course, circle=self.circle
        )

        performance_criteria_1 = create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        completion = mark_course_completion(
            page=performance_criteria_1,
            user=self.member,
            course_session=self.course_session,
            completion_status=CourseCompletionStatus.SUCCESS.value,
        )

        self_evaluation_feedback = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        CourseCompletionFeedback.objects.create(
            feedback=self_evaluation_feedback,
            course_completion=completion,
            feedback_assessment=CourseCompletionStatus.FAIL.value,
        )

        self.client.force_login(self.mentor)

        # WHEN
        response = self.client.get(
            reverse(
                "get_self_evaluation_feedback_as_provider",
                args=[self_evaluation_feedback.learning_unit.id],
            )
        )

        # THEN
        self.assertEqual(response.status_code, 200)

        feedback = response.data
        self.assertEqual(feedback["learning_unit_id"], learning_unit.id)
        self.assertEqual(feedback["title"], learning_unit.title)
        self.assertEqual(feedback["feedback_submitted"], False)
        self.assertEqual(feedback["circle_name"], self.circle.title)  # noqa

        provider_user = feedback["feedback_provider_user"]
        self.assertEqual(provider_user["id"], str(self.mentor.id))  # noqa
        self.assertEqual(provider_user["first_name"], self.mentor.first_name)
        self.assertEqual(provider_user["last_name"], self.mentor.last_name)
        self.assertEqual(provider_user["avatar_url"], self.mentor.avatar_url)

        requester_user = feedback["feedback_requester_user"]
        self.assertEqual(requester_user["id"], str(self.member.id))  # noqa
        self.assertEqual(requester_user["first_name"], self.member.first_name)
        self.assertEqual(requester_user["last_name"], self.member.last_name)
        self.assertEqual(requester_user["avatar_url"], self.member.avatar_url)

        self.assertEqual(len(feedback["criteria"]), 2)

        first_criteria = feedback["criteria"][0]
        self.assertEqual(first_criteria["course_completion_id"], completion.id)
        self.assertEqual(first_criteria["title"], performance_criteria_1.title)
        self.assertEqual(
            first_criteria["self_assessment"],
            CourseCompletionStatus.SUCCESS.value,
        )
        self.assertEqual(
            first_criteria["feedback_assessment"], CourseCompletionStatus.FAIL.value
        )

        second_criteria = feedback["criteria"][1]
        self.assertEqual(second_criteria["course_completion_id"], None)
        self.assertEqual(second_criteria["title"], performance_criteria_1.title)
        self.assertEqual(
            second_criteria["self_assessment"], CourseCompletionStatus.UNKNOWN.value
        )
        self.assertEqual(
            second_criteria["feedback_assessment"],
            CourseCompletionStatus.UNKNOWN.value,
        )

    def test_self_evaluation_feedback_assessment(self):
        # GIVEN
        learning_unit = create_learning_unit(course=self.course, circle=self.circle)

        performance_criteria_1 = create_performance_criteria_page(
            course=self.course,
            course_page=self.course_page,
            circle=self.circle,
            learning_unit=learning_unit,
        )

        completion = mark_course_completion(
            page=performance_criteria_1,
            user=self.member,
            course_session=self.course_session,
            completion_status=CourseCompletionStatus.SUCCESS.value,
        )

        self_evaluation_feedback = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        self.client.force_login(self.mentor)

        # WHEN
        response = self.client.put(
            reverse(
                "add_self_evaluation_feedback_assessment",
                args=[self_evaluation_feedback.id],
            ),
            {
                "course_completion_id": completion.id,
                "feedback_assessment": CourseCompletionStatus.FAIL.value,
            },
        )

        # THEN
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data["success"], True)

        feedback = CourseCompletionFeedback.objects.get(
            feedback=self_evaluation_feedback,
            course_completion=completion,
        )

        self.assertEqual(
            feedback.feedback_assessment, CourseCompletionStatus.FAIL.value
        )

    @patch(
        "vbv_lernwelt.notify.services.NotificationService.send_self_evaluation_feedback_received_notification"
    )
    def test_release_self_evaluation_feedback(self, mock_notification_service_send):
        # GIVEN
        learning_unit = create_learning_unit(course=self.course, circle=self.circle)
        self_evaluation_feedback = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        self.assertEqual(self_evaluation_feedback.feedback_submitted, False)
        self.client.force_login(self.mentor)

        # WHEN
        self.client.put(
            reverse(
                "release_self_evaluation_feedback", args=[self_evaluation_feedback.id]
            ),
        )

        # shall be idempotent
        response = self.client.put(
            reverse(
                "release_self_evaluation_feedback", args=[self_evaluation_feedback.id]
            ),
        )

        # THEN
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data["success"], True)
        self.assertEqual(
            SelfEvaluationFeedback.objects.get(
                id=self_evaluation_feedback.id
            ).feedback_submitted,
            True,
        )

        mock_notification_service_send.assert_called_once_with(
            self_evaluation_feedback=self_evaluation_feedback
        )

    def test_get_self_evaluation_feedback_frontend_urls(self):
        """Makes sure that the frontend urls are correct (used in notifications)"""
        # GIVEN
        learning_unit = create_learning_unit(course=self.course, circle=self.circle)

        cut = create_self_evaluation_feedback(
            learning_unit=learning_unit,
            feedback_requester_user=self.member,
            feedback_provider_user=self.mentor,
        )

        # WHEN
        requester_url = cut.feedback_requester_results_url
        provider_url = cut.feedback_provider_evaluation_url

        # THEN
        _course, _circle, _learning_unit = learning_unit.get_frontend_url_parts()

        # 0 -> no completions so step=0 is correct
        _step = len(learning_unit.performancecriteria_set.all())

        self.assertEqual(
            requester_url,
            f"/course/{_course}/learn/{_circle}/evaluate/{_learning_unit}?step={_step}",
        )

        self.assertEqual(
            provider_url,
            f"/course/{_course}/cockpit/mentor/self-evaluation-feedback/{learning_unit.id}",
        )
