"""
    Tests for enrollment refund capabilities.
"""
from datetime import datetime, timedelta
import ddt
import httpretty
import logging
import pytz
import unittest

from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.client import Client
from django.test.utils import override_settings
from mock import patch

from student.models import CourseEnrollment, CourseEnrollmentAttribute
from student.tests.factories import UserFactory, CourseModeFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase

# These imports refer to lms djangoapps.
# Their testcases are only run under lms.
from certificates.models import CertificateStatuses  # pylint: disable=import-error
from certificates.tests.factories import GeneratedCertificateFactory  # pylint: disable=import-error
from openedx.core.djangoapps.commerce.utils import ECOMMERCE_DATE_FORMAT

# Explicitly import the cache from ConfigurationModel so we can reset it after each test
from config_models.models import cache

log = logging.getLogger(__name__)
TEST_API_URL = 'http://www-internal.example.com/api'
TEST_API_SIGNING_KEY = 'edx'
JSON = 'application/json'


@ddt.ddt
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class RefundableTest(SharedModuleStoreTestCase):
    """
    Tests for dashboard utility functions
    """

    def setUp(self):
        """ Setup components used by each refund test."""
        super(RefundableTest, self).setUp()
        self.course = CourseFactory.create()
        self.user = UserFactory.create(username="jack", email="jack@fake.edx.org", password='test')
        self.verified_mode = CourseModeFactory.create(
            course_id=self.course.id,
            mode_slug='verified',
            mode_display_name='Verified',
            expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1)
        )
        self.enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='verified')

        self.client = Client()
        cache.clear()

    def test_refundable(self):
        """ Assert base case is refundable"""
        self.assertTrue(self.enrollment.refundable())

    def test_refundable_expired_verification(self):
        """ Assert that enrollment is not refundable if course mode has expired."""
        self.verified_mode.expiration_datetime = datetime.now(pytz.UTC) - timedelta(days=1)
        self.verified_mode.save()
        self.assertFalse(self.enrollment.refundable())

        # Assert that can_refund overrides this and allows refund
        self.enrollment.can_refund = True
        self.assertTrue(self.enrollment.refundable())

    def test_refundable_of_purchased_course(self):
        """ Assert that courses without a verified mode are not refundable"""
        self.client.login(username="jack", password="test")
        course = CourseFactory.create()
        CourseModeFactory.create(
            course_id=course.id,
            mode_slug='honor',
            min_price=10,
            currency='usd',
            mode_display_name='honor',
            expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1)
        )
        enrollment = CourseEnrollment.enroll(self.user, course.id, mode='honor')

        # TODO: Until we can allow course administrators to define a refund period for paid for courses show_refund_option should be False. # pylint: disable=fixme
        self.assertFalse(enrollment.refundable())

        resp = self.client.post(reverse('student.views.dashboard', args=[]))
        self.assertIn('You will not be refunded the amount you paid.', resp.content)

    def test_refundable_when_certificate_exists(self):
        """ Assert that enrollment is not refundable once a certificat has been generated."""
        self.assertTrue(self.enrollment.refundable())

        GeneratedCertificateFactory.create(
            user=self.user,
            course_id=self.course.id,
            status=CertificateStatuses.downloadable,
            mode='verified'
        )

        self.assertFalse(self.enrollment.refundable())

        # Assert that can_refund overrides this and allows refund
        self.enrollment.can_refund = True
        self.assertTrue(self.enrollment.refundable())

    def test_refundable_with_cutoff_date(self):
        """ Assert enrollment is refundable before cutoff and not refundable after."""
        self.assertTrue(self.enrollment.refundable())

        with patch('student.models.CourseEnrollment.refund_cutoff_date') as cutoff_date:
            cutoff_date.return_value = datetime.now(pytz.UTC) - timedelta(minutes=5)
            self.assertFalse(self.enrollment.refundable())

            cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(minutes=5)
            self.assertTrue(self.enrollment.refundable())

    @ddt.data(
        (timedelta(days=1), timedelta(days=2), timedelta(days=2), 14),
        (timedelta(days=2), timedelta(days=1), timedelta(days=2), 14),
        (timedelta(days=1), timedelta(days=2), timedelta(days=2), 1),
        (timedelta(days=2), timedelta(days=1), timedelta(days=2), 1),
    )
    @ddt.unpack
    @httpretty.activate
    @override_settings(ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY, ECOMMERCE_API_URL=TEST_API_URL)
    def test_refund_cutoff_date(self, order_date_delta, course_start_delta, expected_date_delta, days):
        """
        Assert that the later date is used with the configurable refund period in calculating the returned cutoff date.
        """
        now = datetime.now(pytz.UTC).replace(microsecond=0)
        order_date = now + order_date_delta
        course_start = now + course_start_delta
        expected_date = now + expected_date_delta
        refund_period = timedelta(days=days)
        order_number = 'OSCR-1000'
        expected_content = '{{"date_placed": "{date}"}}'.format(date=order_date.strftime(ECOMMERCE_DATE_FORMAT))

        httpretty.register_uri(
            httpretty.GET,
            '{url}/orders/{order}/'.format(url=TEST_API_URL, order=order_number),
            status=200, body=expected_content,
            adding_headers={'Content-Type': JSON}
        )

        self.enrollment.course_overview.start = course_start
        self.enrollment.attributes.add(CourseEnrollmentAttribute(
            enrollment=self.enrollment,
            namespace='order',
            name='order_number',
            value=order_number
        ))

        with patch('student.models.EnrollmentRefundConfiguration.current') as config:
            instance = config.return_value
            instance.refund_window = refund_period
            self.assertEqual(
                self.enrollment.refund_cutoff_date(),
                expected_date + refund_period
            )

    def test_refund_cutoff_date_no_attributes(self):
        """ Assert that the None is returned when no order number attribute is found."""
        self.assertIsNone(self.enrollment.refund_cutoff_date())
