# -*- coding: utf-8 -*-
"""
End-to-end tests for the Account Settings page.
"""
from unittest import skip
from nose.plugins.attrib import attr

from bok_choy.web_app_test import WebAppTest

from ...pages.lms.account_settings import AccountSettingsPage
from ...pages.lms.auto_auth import AutoAuthPage
from ...pages.lms.dashboard import DashboardPage

from ..helpers import EventsTestMixin


@attr('shard_5')
class AccountSettingsTestMixin(EventsTestMixin, WebAppTest):
    """
    Mixin with helper methods to test the account settings page.
    """

    CHANGE_INITIATED_EVENT_NAME = u"edx.user.settings.change_initiated"
    USER_SETTINGS_CHANGED_EVENT_NAME = 'edx.user.settings.changed'
    ACCOUNT_SETTINGS_REFERER = u"/account/settings"

    def log_in_as_unique_user(self, email=None):
        """
        Create a unique user and return the account's username and id.
        """
        username = "test_{uuid}".format(uuid=self.unique_id[0:6])
        auto_auth_page = AutoAuthPage(self.browser, username=username, email=email).visit()
        user_id = auto_auth_page.get_user_id()
        return username, user_id

    def settings_changed_event_filter(self, event):
        """Filter out any events that are not "settings changed" events."""
        return event['event_type'] == self.USER_SETTINGS_CHANGED_EVENT_NAME

    def expected_settings_changed_event(self, setting, old, new, table=None):
        """A dictionary representing the expected fields in a "settings changed" event."""
        return {
            'username': self.username,
            'referer': self.get_settings_page_url(),
            'event': {
                'user_id': self.user_id,
                'setting': setting,
                'old': old,
                'new': new,
                'truncated': [],
                'table': table or 'auth_userprofile'
            }
        }

    def settings_change_initiated_event_filter(self, event):
        """Filter out any events that are not "settings change initiated" events."""
        return event['event_type'] == self.CHANGE_INITIATED_EVENT_NAME

    def expected_settings_change_initiated_event(self, setting, old, new, username=None, user_id=None):
        """A dictionary representing the expected fields in a "settings change initiated" event."""
        return {
            'username': username or self.username,
            'referer': self.get_settings_page_url(),
            'event': {
                'user_id': user_id or self.user_id,
                'setting': setting,
                'old': old,
                'new': new,
            }
        }

    def get_settings_page_url(self):
        """The absolute URL of the account settings page given the test context."""
        return self.relative_path_to_absolute_uri(self.ACCOUNT_SETTINGS_REFERER)

    def assert_no_setting_changed_event(self):
        """Assert no setting changed event has been emitted thus far."""
        self.assert_no_matching_events_were_emitted({'event_type': self.USER_SETTINGS_CHANGED_EVENT_NAME})


@attr('shard_5')
class DashboardMenuTest(AccountSettingsTestMixin, WebAppTest):
    """
    Tests that the dashboard menu works correctly with the account settings page.
    """
    def test_link_on_dashboard_works(self):
        """
        Scenario: Verify that the "Account Settings" link works from the dashboard.


        Given that I am a registered user
        And I visit my dashboard
        And I click on "Account Settings" in the top drop down
        Then I should see my account settings page
        """
        self.log_in_as_unique_user()
        dashboard_page = DashboardPage(self.browser)
        dashboard_page.visit()
        dashboard_page.click_username_dropdown()
        self.assertIn('Account Settings', dashboard_page.username_dropdown_link_text)
        dashboard_page.click_account_settings_link()


@attr('shard_5')
class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
    """
    Tests that verify behaviour of the Account Settings page.
    """
    SUCCESS_MESSAGE = 'Your changes have been saved.'

    def setUp(self):
        """
        Initialize account and pages.
        """
        super(AccountSettingsPageTest, self).setUp()
        self.username, self.user_id = self.log_in_as_unique_user()
        self.visit_account_settings_page()

    def visit_account_settings_page(self):
        """
        Visit the account settings page for the current user.
        """
        self.account_settings_page = AccountSettingsPage(self.browser)
        self.account_settings_page.visit()
        self.account_settings_page.wait_for_ajax()

    def test_page_view_event(self):
        """
        Scenario: An event should be recorded when the "Account Settings"
           page is viewed.

        Given that I am a registered user
        And I visit my account settings page
        Then a page view analytics event should be recorded
        """

        actual_events = self.wait_for_events(
            event_filter={'event_type': 'edx.user.settings.viewed'}, number_of_matches=1)
        self.assert_events_match(
            [
                {
                    'event': {
                        'user_id': self.user_id,
                        'page': 'account',
                        'visibility': None
                    }
                }
            ],
            actual_events
        )

    def test_all_sections_and_fields_are_present(self):
        """
        Scenario: Verify that all sections and fields are present on the page.
        """
        expected_sections_structure = [
            {
                'title': 'Basic Account Information (required)',
                'fields': [
                    'Username',
                    'Full Name',
                    'Email Address',
                    'Password',
                    'Language',
                    'Country or Region'
                ]
            },
            {
                'title': 'Additional Information (optional)',
                'fields': [
                    'Education Completed',
                    'Gender',
                    'Year of Birth',
                    'Preferred Language',
                ]
            },
            {
                'title': 'Connected Accounts',
                'fields': [
                    'Facebook',
                    'Google',
                ]
            }
        ]

        self.assertEqual(self.account_settings_page.sections_structure(), expected_sections_structure)

    def _test_readonly_field(self, field_id, title, value):
        """
        Test behavior of a readonly field.
        """
        self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
        self.assertEqual(self.account_settings_page.value_for_readonly_field(field_id), value)

    def _test_text_field(
            self, field_id, title, initial_value, new_invalid_value, new_valid_values, success_message=SUCCESS_MESSAGE,
            assert_after_reload=True
    ):
        """
        Test behaviour of a text field.
        """
        self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
        self.assertEqual(self.account_settings_page.value_for_text_field(field_id), initial_value)

        self.assertEqual(
            self.account_settings_page.value_for_text_field(field_id, new_invalid_value), new_invalid_value
        )
        self.account_settings_page.wait_for_indicator(field_id, 'validation-error')
        self.browser.refresh()
        self.assertNotEqual(self.account_settings_page.value_for_text_field(field_id), new_invalid_value)

        for new_value in new_valid_values:
            self.assertEqual(self.account_settings_page.value_for_text_field(field_id, new_value), new_value)
            self.account_settings_page.wait_for_messsage(field_id, success_message)
            if assert_after_reload:
                self.browser.refresh()
                self.assertEqual(self.account_settings_page.value_for_text_field(field_id), new_value)

    def _test_dropdown_field(
            self, field_id, title, initial_value, new_values, success_message=SUCCESS_MESSAGE, reloads_on_save=False
    ):
        """
        Test behaviour of a dropdown field.
        """
        self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
        self.assertEqual(self.account_settings_page.value_for_dropdown_field(field_id), initial_value)

        for new_value in new_values:
            self.assertEqual(self.account_settings_page.value_for_dropdown_field(field_id, new_value), new_value)
            self.account_settings_page.wait_for_messsage(field_id, success_message)
            if reloads_on_save:
                self.account_settings_page.wait_for_loading_indicator()
            else:
                self.browser.refresh()
                self.account_settings_page.wait_for_page()
            self.assertEqual(self.account_settings_page.value_for_dropdown_field(field_id), new_value)

    def _test_link_field(self, field_id, title, link_title, success_message):
        """
        Test behaviour a link field.
        """
        self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
        self.assertEqual(self.account_settings_page.link_title_for_link_field(field_id), link_title)
        self.account_settings_page.click_on_link_in_link_field(field_id)
        self.account_settings_page.wait_for_messsage(field_id, success_message)

    def test_username_field(self):
        """
        Test behaviour of "Username" field.
        """
        self._test_readonly_field('username', 'Username', self.username)

    def test_full_name_field(self):
        """
        Test behaviour of "Full Name" field.
        """
        self._test_text_field(
            u'name',
            u'Full Name',
            self.username,
            u'@',
            [u'another name', self.username],
        )

        actual_events = self.wait_for_events(event_filter=self.settings_changed_event_filter, number_of_matches=2)
        self.assert_events_match(
            [
                self.expected_settings_changed_event('name', self.username, 'another name'),
                self.expected_settings_changed_event('name', 'another name', self.username),
            ],
            actual_events
        )

    def test_email_field(self):
        """
        Test behaviour of "Email" field.
        """
        email = u"test@example.com"
        username, user_id = self.log_in_as_unique_user(email=email)
        self.visit_account_settings_page()
        self._test_text_field(
            u'email',
            u'Email Address',
            email,
            u'@',
            [u'me@here.com', u'you@there.com'],
            success_message='Click the link in the message to update your email address.',
            assert_after_reload=False
        )

        actual_events = self.wait_for_events(
            event_filter=self.settings_change_initiated_event_filter, number_of_matches=2)
        self.assert_events_match(
            [
                self.expected_settings_change_initiated_event(
                    'email', email, 'me@here.com', username=username, user_id=user_id),
                # NOTE the first email change was never confirmed, so old has not changed.
                self.expected_settings_change_initiated_event(
                    'email', email, 'you@there.com', username=username, user_id=user_id),
            ],
            actual_events
        )
        # Email is not saved until user confirms, so no events should have been
        # emitted.
        self.assert_no_setting_changed_event()

    def test_password_field(self):
        """
        Test behaviour of "Password" field.
        """
        self._test_link_field(
            u'password',
            u'Password',
            u'Reset Password',
            success_message='Click the link in the message to reset your password.',
        )

        event_filter = self.expected_settings_change_initiated_event('password', None, None)
        self.wait_for_events(event_filter=event_filter, number_of_matches=1)
        # Like email, since the user has not confirmed their password change,
        # the field has not yet changed, so no events will have been emitted.
        self.assert_no_setting_changed_event()

    @skip(
        'On bokchoy test servers, language changes take a few reloads to fully realize '
        'which means we can no longer reliably match the strings in the html in other tests.'
    )
    def test_language_field(self):
        """
        Test behaviour of "Language" field.
        """
        self._test_dropdown_field(
            u'pref-lang',
            u'Language',
            u'English',
            [u'Dummy Language (Esperanto)', u'English'],
            reloads_on_save=True,
        )

    def test_education_completed_field(self):
        """
        Test behaviour of "Education Completed" field.
        """
        self._test_dropdown_field(
            u'level_of_education',
            u'Education Completed',
            u'',
            [u'Bachelor\'s degree', u''],
        )

        actual_events = self.wait_for_events(event_filter=self.settings_changed_event_filter, number_of_matches=2)
        self.assert_events_match(
            [
                self.expected_settings_changed_event('level_of_education', None, 'b'),
                self.expected_settings_changed_event('level_of_education', 'b', None),
            ],
            actual_events
        )

    def test_gender_field(self):
        """
        Test behaviour of "Gender" field.
        """
        self._test_dropdown_field(
            u'gender',
            u'Gender',
            u'',
            [u'Female', u''],
        )

        actual_events = self.wait_for_events(event_filter=self.settings_changed_event_filter, number_of_matches=2)
        self.assert_events_match(
            [
                self.expected_settings_changed_event('gender', None, 'f'),
                self.expected_settings_changed_event('gender', 'f', None),
            ],
            actual_events
        )

    def test_year_of_birth_field(self):
        """
        Test behaviour of "Year of Birth" field.
        """
        # Note that when we clear the year_of_birth here we're firing an event.
        self.assertEqual(self.account_settings_page.value_for_dropdown_field('year_of_birth', ''), '')

        expected_events = [
            self.expected_settings_changed_event('year_of_birth', None, 1980),
            self.expected_settings_changed_event('year_of_birth', 1980, None),
        ]
        with self.assert_events_match_during(self.settings_changed_event_filter, expected_events):
            self._test_dropdown_field(
                u'year_of_birth',
                u'Year of Birth',
                u'',
                [u'1980', u''],
            )

    def test_country_field(self):
        """
        Test behaviour of "Country or Region" field.
        """
        self._test_dropdown_field(
            u'country',
            u'Country or Region',
            u'',
            [u'Pakistan', u'Palau'],
        )

    def test_preferred_language_field(self):
        """
        Test behaviour of "Preferred Language" field.
        """
        self._test_dropdown_field(
            u'language_proficiencies',
            u'Preferred Language',
            u'',
            [u'Pushto', u''],
        )

        actual_events = self.wait_for_events(event_filter=self.settings_changed_event_filter, number_of_matches=2)
        self.assert_events_match(
            [
                self.expected_settings_changed_event(
                    'language_proficiencies', [], [{'code': 'ps'}], table='student_languageproficiency'),
                self.expected_settings_changed_event(
                    'language_proficiencies', [{'code': 'ps'}], [], table='student_languageproficiency'),
            ],
            actual_events
        )

    def test_connected_accounts(self):
        """
        Test that fields for third party auth providers exist.

        Currently there is no way to test the whole authentication process
        because that would require accounts with the providers.
        """
        for field_id, title, link_title in [
            ['auth-facebook', 'Facebook', 'Link'],
            ['auth-google', 'Google', 'Link'],
        ]:
            self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
            self.assertEqual(self.account_settings_page.link_title_for_link_field(field_id), link_title)
