"""
Tests for credit courses on the student dashboard.
"""


import datetime
import unittest

import ddt
import pytz
from django.conf import settings
from django.test.utils import override_settings
from django.urls import reverse
from mock import patch

from openedx.core.djangoapps.credit import api as credit_api
from openedx.core.djangoapps.credit.models import CreditCourse, CreditEligibility, CreditProvider
from student.models import CourseEnrollmentAttribute
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory

TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"


@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
    "hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY,
})
@patch.dict(settings.FEATURES, {"ENABLE_CREDIT_ELIGIBILITY": True})
@ddt.ddt
class CreditCourseDashboardTest(ModuleStoreTestCase):
    """
    Tests for credit courses on the student dashboard.
    """

    USERNAME = "ron"
    PASSWORD = "mobiliarbus"

    PROVIDER_ID = "hogwarts"
    PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
    PROVIDER_STATUS_URL = "http://credit.example.com/status"

    def setUp(self):
        """Create a course and an enrollment. """
        super(CreditCourseDashboardTest, self).setUp()

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
        result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Create a course and configure it as a credit course
        self.course = CourseFactory()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)  # pylint: disable=no-member

        # Configure a credit provider
        CreditProvider.objects.create(
            provider_id=self.PROVIDER_ID,
            display_name=self.PROVIDER_NAME,
            provider_status_url=self.PROVIDER_STATUS_URL,
            enable_integration=True,
        )

        # Configure a single credit requirement (minimum passing grade)
        credit_api.set_credit_requirements(
            self.course.id,  # pylint: disable=no-member
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Final Grade",
                    "criteria": {
                        "min_grade": 0.8
                    }
                }
            ]
        )

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,  # pylint: disable=no-member
            mode="verified"
        )

    def test_not_eligible_for_credit(self):
        # The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
        response = self._load_dashboard()
        self.assertNotContains(response, "credit-eligibility-msg")
        self.assertNotContains(response, "purchase-credit-btn")

    def test_eligible_for_credit(self):
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        response = self._load_dashboard()
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")

        # Move the eligibility deadline so it's within 30 days
        eligibility = CreditEligibility.objects.get(username=self.USERNAME)
        eligibility.deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=29)
        eligibility.save()

        # The user should still have the option to purchase credit,
        # but there should also be a message urging the user to purchase soon.

        response = self._load_dashboard()

        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response, "You have completed this course and are eligible")

    def test_purchased_credit(self):
        # Simulate that the user has purchased credit, but has not
        # yet initiated a request to the credit provider
        self._make_eligible()
        self._purchase_credit()

        response = self._load_dashboard()
        self.assertContains(response, "credit-request-not-started-msg")

    def test_purchased_credit_and_request_pending(self):
        # Simulate that the user has purchased credit and initiated a request,
        # but we haven't yet heard back from the credit provider.
        self._make_eligible()
        self._purchase_credit()
        self._initiate_request()

        # Expect that the user's status is "pending"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-pending-msg")

    def test_purchased_credit_and_request_approved(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request approved by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "approved")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-approved-msg")

    def test_purchased_credit_and_request_rejected(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request rejected by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "rejected")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-rejected-msg")

    def test_credit_status_error(self):
        # Simulate an error condition: the user has a credit enrollment
        # but no enrollment attribute indicating which provider the user
        # purchased credit from.
        self._make_eligible()
        self._purchase_credit()
        CourseEnrollmentAttribute.objects.all().delete()

        # Expect an error message
        response = self._load_dashboard()
        self.assertContains(response, "credit-error-msg")

    def _load_dashboard(self):
        """Load the student dashboard and return the HttpResponse. """
        return self.client.get(reverse("dashboard"))

    def _make_eligible(self):
        """Make the user eligible for credit in the course. """
        credit_api.set_credit_requirement_status(
            self.user,
            self.course.id,  # pylint: disable=no-member
            "grade", "grade",
            status="satisfied",
            reason={
                "final_grade": 0.95
            }
        )

    def _purchase_credit(self):
        """Purchase credit from a provider in the course. """
        self.enrollment.mode = "credit"
        self.enrollment.save()  # pylint: disable=no-member

        CourseEnrollmentAttribute.objects.create(
            enrollment=self.enrollment,
            namespace="credit",
            name="provider_id",
            value=self.PROVIDER_ID,
        )

    def _initiate_request(self):
        """Initiate a request for credit from a provider. """
        request = credit_api.create_credit_request(
            self.course.id,  # pylint: disable=no-member
            self.PROVIDER_ID,
            self.USERNAME
        )
        return request["parameters"]["request_uuid"]

    def _set_request_status(self, uuid, status):
        """Set the status of a request for credit, simulating the notification from the provider. """
        credit_api.update_credit_request_status(uuid, self.PROVIDER_ID, status)

    @ddt.data(
        (
            [u'Arizona State University'],
            'You are now eligible for credit from Arizona State University'),
        (
            [u'Arizona State University', u'Hogwarts School of Witchcraft'],
            'You are now eligible for credit from Arizona State University and Hogwarts School of Witchcraft'
        ),
        (
            [u'Arizona State University', u'Hogwarts School of Witchcraft and Wizardry', u'Charter Oak'],
            'You are now eligible for credit from Arizona State University, Hogwarts School'
            ' of Witchcraft and Wizardry, and Charter Oak'
        ),
        ([], 'You have completed this course and are eligible'),
        (None, 'You have completed this course and are eligible')
    )
    @ddt.unpack
    def test_eligible_for_credit_with_providers_names(self, providers_list, credit_string):
        """Verify the message on dashboard with different number of providers."""
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        with patch('student.views.dashboard.get_credit_provider_attribute_values') as mock_method:
            mock_method.return_value = providers_list
            response = self._load_dashboard()

        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response, credit_string)
