"""Ensure emitted events contain the fields legacy processors expect to find."""

from collections import namedtuple

import ddt
from mock import sentinel
from django.test.utils import override_settings

from openedx.core.lib.tests.assertions.events import assert_events_equal

from . import EventTrackingTestCase, FROZEN_TIME
from ..shim import PrefixedEventProcessor
from .. import transformers


LEGACY_SHIM_PROCESSOR = [
    {
        'ENGINE': 'track.shim.LegacyFieldMappingProcessor'
    }
]

GOOGLE_ANALYTICS_PROCESSOR = [
    {
        'ENGINE': 'track.shim.GoogleAnalyticsProcessor'
    }
]


@override_settings(
    EVENT_TRACKING_PROCESSORS=LEGACY_SHIM_PROCESSOR,
)
class LegacyFieldMappingProcessorTestCase(EventTrackingTestCase):
    """Ensure emitted events contain the fields legacy processors expect to find."""

    def test_event_field_mapping(self):
        data = {sentinel.key: sentinel.value}

        context = {
            'accept_language': sentinel.accept_language,
            'referer': sentinel.referer,
            'username': sentinel.username,
            'session': sentinel.session,
            'ip': sentinel.ip,
            'host': sentinel.host,
            'agent': sentinel.agent,
            'path': sentinel.path,
            'user_id': sentinel.user_id,
            'course_id': sentinel.course_id,
            'org_id': sentinel.org_id,
            'client_id': sentinel.client_id,
        }
        with self.tracker.context('test', context):
            self.tracker.emit(sentinel.name, data)

        emitted_event = self.get_event()

        expected_event = {
            'accept_language': sentinel.accept_language,
            'referer': sentinel.referer,
            'event_type': sentinel.name,
            'name': sentinel.name,
            'context': {
                'user_id': sentinel.user_id,
                'course_id': sentinel.course_id,
                'org_id': sentinel.org_id,
                'path': sentinel.path,
            },
            'event': data,
            'username': sentinel.username,
            'event_source': 'server',
            'time': FROZEN_TIME,
            'agent': sentinel.agent,
            'host': sentinel.host,
            'ip': sentinel.ip,
            'page': None,
            'session': sentinel.session,
        }
        assert_events_equal(expected_event, emitted_event)

    def test_missing_fields(self):
        self.tracker.emit(sentinel.name)

        emitted_event = self.get_event()

        expected_event = {
            'accept_language': '',
            'referer': '',
            'event_type': sentinel.name,
            'name': sentinel.name,
            'context': {},
            'event': {},
            'username': '',
            'event_source': 'server',
            'time': FROZEN_TIME,
            'agent': '',
            'host': '',
            'ip': '',
            'page': None,
            'session': '',
        }
        assert_events_equal(expected_event, emitted_event)


@override_settings(
    EVENT_TRACKING_PROCESSORS=GOOGLE_ANALYTICS_PROCESSOR,
)
class GoogleAnalyticsProcessorTestCase(EventTrackingTestCase):
    """Ensure emitted events contain the fields necessary for Google Analytics."""

    def test_event_fields(self):
        """ Test that course_id is added as the label if present, and nonInteraction is set. """
        data = {sentinel.key: sentinel.value}

        context = {
            'path': sentinel.path,
            'user_id': sentinel.user_id,
            'course_id': sentinel.course_id,
            'org_id': sentinel.org_id,
            'client_id': sentinel.client_id,
        }
        with self.tracker.context('test', context):
            self.tracker.emit(sentinel.name, data)

        emitted_event = self.get_event()

        expected_event = {
            'context': context,
            'data': data,
            'label': sentinel.course_id,
            'name': sentinel.name,
            'nonInteraction': 1,
            'timestamp': FROZEN_TIME,
        }
        assert_events_equal(expected_event, emitted_event)

    def test_no_course_id(self):
        """ Test that a label is not added if course_id is not specified, but nonInteraction is still set. """
        data = {sentinel.key: sentinel.value}

        context = {
            'path': sentinel.path,
            'user_id': sentinel.user_id,
            'client_id': sentinel.client_id,
        }
        with self.tracker.context('test', context):
            self.tracker.emit(sentinel.name, data)

        emitted_event = self.get_event()

        expected_event = {
            'context': context,
            'data': data,
            'name': sentinel.name,
            'nonInteraction': 1,
            'timestamp': FROZEN_TIME,
        }
        assert_events_equal(expected_event, emitted_event)


@override_settings(
    EVENT_TRACKING_BACKENDS={
        '0': {
            'ENGINE': 'eventtracking.backends.routing.RoutingBackend',
            'OPTIONS': {
                'backends': {
                    'first': {'ENGINE': 'track.tests.InMemoryBackend'}
                },
                'processors': [
                    {
                        'ENGINE': 'track.shim.GoogleAnalyticsProcessor'
                    }
                ]
            }
        },
        '1': {
            'ENGINE': 'eventtracking.backends.routing.RoutingBackend',
            'OPTIONS': {
                'backends': {
                    'second': {
                        'ENGINE': 'track.tests.InMemoryBackend'
                    }
                }
            }
        }
    }
)
class MultipleShimGoogleAnalyticsProcessorTestCase(EventTrackingTestCase):
    """Ensure changes don't impact other backends"""

    def test_multiple_backends(self):
        data = {
            sentinel.key: sentinel.value,
        }

        context = {
            'path': sentinel.path,
            'user_id': sentinel.user_id,
            'course_id': sentinel.course_id,
            'org_id': sentinel.org_id,
            'client_id': sentinel.client_id,
        }
        with self.tracker.context('test', context):
            self.tracker.emit(sentinel.name, data)

        segment_emitted_event = self.tracker.backends['0'].backends['first'].events[0]
        log_emitted_event = self.tracker.backends['1'].backends['second'].events[0]

        expected_event = {
            'context': context,
            'data': data,
            'label': sentinel.course_id,
            'name': sentinel.name,
            'nonInteraction': 1,
            'timestamp': FROZEN_TIME,
        }
        assert_events_equal(expected_event, segment_emitted_event)

        expected_event = {
            'context': context,
            'data': data,
            'name': sentinel.name,
            'timestamp': FROZEN_TIME,
        }
        assert_events_equal(expected_event, log_emitted_event)


SequenceDDT = namedtuple('SequenceDDT', ['action', 'tab_count', 'current_tab', 'legacy_event_type'])


@ddt.ddt
class EventTransformerRegistryTestCase(EventTrackingTestCase):
    """
    Test the behavior of the event registry
    """

    def setUp(self):
        super(EventTransformerRegistryTestCase, self).setUp()
        self.registry = transformers.EventTransformerRegistry()

    @ddt.data(
        ('edx.ui.lms.sequence.next_selected', transformers.NextSelectedEventTransformer),
        ('edx.ui.lms.sequence.previous_selected', transformers.PreviousSelectedEventTransformer),
        ('edx.ui.lms.sequence.tab_selected', transformers.SequenceTabSelectedEventTransformer),
        ('edx.video.foo.bar', transformers.VideoEventTransformer),
    )
    @ddt.unpack
    def test_event_registry_dispatch(self, event_name, expected_transformer):
        event = {'name': event_name}
        transformer = self.registry.create_transformer(event)
        self.assertIsInstance(transformer, expected_transformer)

    @ddt.data(
        'edx.ui.lms.sequence.next_selected.what',
        'edx',
        'unregistered_event',
    )
    def test_dispatch_to_nonexistent_events(self, event_name):
        event = {'name': event_name}
        with self.assertRaises(KeyError):
            self.registry.create_transformer(event)


@ddt.ddt
class PrefixedEventProcessorTestCase(EventTrackingTestCase):
    """
    Test PrefixedEventProcessor
    """

    @ddt.data(
        SequenceDDT(action=u'next', tab_count=5, current_tab=3, legacy_event_type=u'seq_next'),
        SequenceDDT(action=u'next', tab_count=5, current_tab=5, legacy_event_type=None),
        SequenceDDT(action=u'previous', tab_count=5, current_tab=3, legacy_event_type=u'seq_prev'),
        SequenceDDT(action=u'previous', tab_count=5, current_tab=1, legacy_event_type=None),
    )
    def test_sequence_linear_navigation(self, sequence_ddt):
        event_name = u'edx.ui.lms.sequence.{}_selected'.format(sequence_ddt.action)

        event = {
            u'name': event_name,
            u'event': {
                u'current_tab': sequence_ddt.current_tab,
                u'tab_count': sequence_ddt.tab_count,
                u'id': u'ABCDEFG',
            }
        }

        process_event_shim = PrefixedEventProcessor()
        result = process_event_shim(event)

        # Legacy fields get added when needed
        if sequence_ddt.action == u'next':
            offset = 1
        else:
            offset = -1
        if sequence_ddt.legacy_event_type:
            self.assertEqual(result[u'event_type'], sequence_ddt.legacy_event_type)
            self.assertEqual(result[u'event'][u'old'], sequence_ddt.current_tab)
            self.assertEqual(result[u'event'][u'new'], sequence_ddt.current_tab + offset)
        else:
            self.assertNotIn(u'event_type', result)
            self.assertNotIn(u'old', result[u'event'])
            self.assertNotIn(u'new', result[u'event'])

    def test_sequence_tab_navigation(self):
        event_name = u'edx.ui.lms.sequence.tab_selected'
        event = {
            u'name': event_name,
            u'event': {
                u'current_tab': 2,
                u'target_tab': 5,
                u'tab_count': 9,
                u'id': u'block-v1:abc',
                u'widget_placement': u'top',
            }
        }

        process_event_shim = PrefixedEventProcessor()
        result = process_event_shim(event)
        self.assertEqual(result[u'event_type'], u'seq_goto')
        self.assertEqual(result[u'event'][u'old'], 2)
        self.assertEqual(result[u'event'][u'new'], 5)