"""
Tests the execution of forum notification tasks.
"""
from contextlib import contextmanager
from datetime import datetime, timedelta
import json
import math
from urlparse import urljoin

import ddt
from django.conf import settings
from django.contrib.sites.models import Site
import mock

from django_comment_common.models import ForumsConfig
from django_comment_common.signals import comment_created
from edx_ace.recipient import Recipient
from edx_ace.utils import date
from lms.djangoapps.discussion.config.waffle import waffle, FORUM_RESPONSE_NOTIFICATIONS, SEND_NOTIFICATIONS_FOR_COURSE
from lms.djangoapps.discussion.signals.handlers import ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY
from lms.djangoapps.discussion.tasks import _should_send_message, _generate_ga_pixel_url
import lms.lib.comment_client as cc
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
from openedx.core.djangoapps.schedules.template_context import get_base_template_context
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase

NOW = datetime.utcnow()
ONE_HOUR_AGO = NOW - timedelta(hours=1)
TWO_HOURS_AGO = NOW - timedelta(hours=2)


def make_mock_responder(subscribed_thread_ids=None, thread_data=None, comment_data=None, per_page=1):
    def mock_subscribed_threads(method, url, **kwargs):
        subscribed_thread_collection = [
            {'id': thread_id} for thread_id in subscribed_thread_ids
        ]
        page = kwargs.get('params', {}).get('page', 1)
        start_index = per_page * (page - 1)
        end_index = per_page * page
        data = {
            'collection': subscribed_thread_collection[start_index: end_index],
            'page': page,
            'num_pages': int(math.ceil(len(subscribed_thread_collection) / float(per_page))),
            'thread_count': len(subscribed_thread_collection)
        }
        return mock.Mock(status_code=200, text=json.dumps(data), json=mock.Mock(return_value=data))

    def mock_comment_find(method, url, **kwargs):
        return mock.Mock(status_code=200, text=json.dumps(comment_data), json=mock.Mock(return_value=comment_data))

    def mock_thread_find(method, url, **kwargs):
        return mock.Mock(status_code=200, text=json.dumps(thread_data), json=mock.Mock(return_value=thread_data))

    def mock_request(method, url, **kwargs):
        if '/subscribed_threads' in url:
            return mock_subscribed_threads(method, url, **kwargs)
        if '/comments' in url:
            return mock_comment_find(method, url, **kwargs)
        if '/threads' in url:
            return mock_thread_find(method, url, **kwargs)

    return mock_request


@ddt.ddt
class TaskTestCase(ModuleStoreTestCase):
    @classmethod
    @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
    def setUpClass(cls):
        super(TaskTestCase, cls).setUpClass()
        cls.discussion_id = 'dummy_discussion_id'
        cls.course = CourseOverviewFactory.create(language='fr')

        # Patch the comment client user save method so it does not try
        # to create a new cc user when creating a django user
        with mock.patch('student.models.cc.User.save'):
            cls.thread_author = UserFactory(
                username='thread_author',
                password='password',
                email='email'
            )
            cls.comment_author = UserFactory(
                username='comment_author',
                password='password',
                email='email'
            )

            CourseEnrollmentFactory(
                user=cls.thread_author,
                course_id=cls.course.id
            )
            CourseEnrollmentFactory(
                user=cls.comment_author,
                course_id=cls.course.id
            )

        config = ForumsConfig.current()
        config.enabled = True
        config.save()

        cls.create_thread_and_comments()

    @classmethod
    def create_thread_and_comments(cls):
        cls.thread = {
            'id': cls.discussion_id,
            'course_id': unicode(cls.course.id),
            'created_at': date.serialize(TWO_HOURS_AGO),
            'title': 'thread-title',
            'user_id': cls.thread_author.id,
            'username': cls.thread_author.username,
            'commentable_id': 'thread-commentable-id',
        }
        cls.comment = {
            'id': 'comment',
            'body': 'comment-body',
            'created_at': date.serialize(ONE_HOUR_AGO),
            'thread_id': cls.thread['id'],
            'parent_id': None,
            'user_id': cls.comment_author.id,
            'username': cls.comment_author.username,
        }
        cls.comment2 = {
            'id': 'comment2',
            'body': 'comment2-body',
            'created_at': date.serialize(NOW),
            'thread_id': cls.thread['id'],
            'parent_id': None,
            'user_id': cls.comment_author.id,
            'username': cls.comment_author.username
        }
        cls.subcomment = {
            'id': 'subcomment',
            'body': 'subcomment-body',
            'created_at': date.serialize(NOW),
            'thread_id': cls.thread['id'],
            'parent_id': cls.comment['id'],
            'user_id': cls.comment_author.id,
            'username': cls.comment_author.username,
        }
        cls.thread['children'] = [cls.comment, cls.comment2]
        cls.comment['child_count'] = 1,

    def setUp(self):
        super(TaskTestCase, self).setUp()
        self.request_patcher = mock.patch('requests.request')
        self.mock_request = self.request_patcher.start()

        self.ace_send_patcher = mock.patch('edx_ace.ace.send')
        self.mock_ace_send = self.ace_send_patcher.start()

        thread_permalink = '/courses/discussion/dummy_discussion_id'
        self.permalink_patcher = mock.patch('lms.djangoapps.discussion.tasks.permalink', return_value=thread_permalink)
        self.mock_permalink = self.permalink_patcher.start()

    def tearDown(self):
        super(TaskTestCase, self).tearDown()
        self.request_patcher.stop()
        self.ace_send_patcher.stop()
        self.permalink_patcher.stop()

    @ddt.data(True, False)
    @override_waffle_flag(SEND_NOTIFICATIONS_FOR_COURSE, True)
    def test_send_discussion_email_notification(self, user_subscribed):
        if user_subscribed:
            non_matching_id = 'not-a-match'
            # with per_page left with a default value of 1, this ensures
            # that we test a multiple page result when calling
            # comment_client.User.subscribed_threads()
            subscribed_thread_ids = [non_matching_id, self.discussion_id]
        else:
            subscribed_thread_ids = []

        self.mock_request.side_effect = make_mock_responder(
            subscribed_thread_ids=subscribed_thread_ids,
            comment_data=self.comment,
            thread_data=self.thread,
        )
        user = mock.Mock()
        comment = cc.Comment.find(id=self.comment['id']).retrieve()
        site = Site.objects.get_current()
        site_config = SiteConfigurationFactory.create(site=site)
        site_config.values[ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY] = True
        site_config.save()
        with waffle().override(FORUM_RESPONSE_NOTIFICATIONS):
            with mock.patch('lms.djangoapps.discussion.signals.handlers.get_current_site', return_value=site):
                comment_created.send(sender=None, user=user, post=comment)

        if user_subscribed:
            expected_message_context = get_base_template_context(site)
            expected_message_context.update({
                'comment_author_id': self.comment_author.id,
                'comment_body': self.comment['body'],
                'comment_created_at': ONE_HOUR_AGO,
                'comment_id': self.comment['id'],
                'comment_username': self.comment_author.username,
                'course_id': self.course.id,
                'thread_author_id': self.thread_author.id,
                'thread_created_at': TWO_HOURS_AGO,
                'thread_id': self.discussion_id,
                'thread_title': 'thread-title',
                'thread_username': self.thread_author.username,
                'thread_commentable_id': self.thread['commentable_id'],
                'post_link': urljoin(site.domain, self.mock_permalink.return_value),
                'site': site,
                'site_id': site.id
            })
            expected_message_context['ga_tracking_pixel_url'] = _generate_ga_pixel_url(expected_message_context)
            expected_recipient = Recipient(self.thread_author.username, self.thread_author.email)
            actual_message = self.mock_ace_send.call_args_list[0][0][0]
            self.assertEqual(expected_message_context, actual_message.context)
            self.assertEqual(expected_recipient, actual_message.recipient)
            self.assertEqual(self.course.language, actual_message.language)
        else:
            self.assertFalse(self.mock_ace_send.called)

    @override_waffle_flag(SEND_NOTIFICATIONS_FOR_COURSE, True)
    def run_should_not_send_email_test(self, comment_dict):
        self.mock_request.side_effect = make_mock_responder(
            subscribed_thread_ids=[self.discussion_id],
            comment_data=comment_dict,
            thread_data=self.thread,
        )
        user = mock.Mock()
        comment = cc.Comment.find(id=comment_dict['id']).retrieve()
        with waffle().override(FORUM_RESPONSE_NOTIFICATIONS):
            comment_created.send(sender=None, user=user, post=comment)

        actual_result = _should_send_message({
            'thread_author_id': self.thread_author.id,
            'course_id': self.course.id,
            'comment_id': comment_dict['id'],
            'thread_id': self.thread['id'],
        })
        self.assertEqual(actual_result, False)
        self.assertFalse(self.mock_ace_send.called)

    def test_subcomment_should_not_send_email(self):
        self.run_should_not_send_email_test(self.subcomment)

    def test_second_comment_should_not_send_email(self):
        self.run_should_not_send_email_test(self.comment2)
