# pylint: disable=missing-docstring
import datetime
import os

import pytz
from django.conf import settings
from lettuce import step, world
from mock import patch
from nose.tools import assert_equal, assert_in, assert_is_none, assert_true
from pytz import UTC
from selenium.common.exceptions import NoAlertPresentException
from splinter.exceptions import ElementDoesNotExist

from common import visit_scenario_item
from courseware.access import has_access
from courseware.tests.factories import BetaTesterFactory, InstructorFactory
from student.tests.factories import UserFactory

TEST_COURSE_NAME = "test_course_a"


@step('I view the LTI and error is shown$')
def lti_is_not_rendered(_step):
    # error is shown
    assert world.is_css_present('.error_message', wait_time=0)

    # iframe is not presented
    assert not world.is_css_present('iframe', wait_time=0)

    # link is not presented
    assert not world.is_css_present('.link_lti_new_window', wait_time=0)


def check_lti_iframe_content(text):
    # inside iframe test content is presented
    location = world.scenario_dict['LTI'].location.html_id()
    iframe_name = 'ltiFrame-' + location
    with world.browser.get_iframe(iframe_name) as iframe:
        # iframe does not contain functions from terrain/ui_helpers.py
        assert iframe.is_element_present_by_css('.result', wait_time=0)
        assert (text == world.retry_on_exception(
            lambda: iframe.find_by_css('.result')[0].text,
            max_attempts=5
        ))


@step('I view the LTI and it is rendered in (.*)$')
def lti_is_rendered(_step, rendered_in):
    if rendered_in.strip() == 'iframe':
        world.wait_for_present('iframe')
        assert world.is_css_present('iframe', wait_time=2)
        assert not world.is_css_present('.link_lti_new_window', wait_time=0)
        assert not world.is_css_present('.error_message', wait_time=0)

        # iframe is visible
        assert world.css_visible('iframe')
        check_lti_iframe_content("This is LTI tool. Success.")

    elif rendered_in.strip() == 'new page':
        assert not world.is_css_present('iframe', wait_time=2)
        assert world.is_css_present('.link_lti_new_window', wait_time=0)
        assert not world.is_css_present('.error_message', wait_time=0)
        click_and_check_lti_popup()
    else:  # incorrect rendered_in parameter
        assert False


@step('I view the permission alert$')
def view_lti_permission_alert(_step):
    assert not world.is_css_present('iframe', wait_time=2)
    assert world.is_css_present('.link_lti_new_window', wait_time=0)
    assert not world.is_css_present('.error_message', wait_time=0)
    world.css_find('.link_lti_new_window').first.click()
    alert = world.browser.get_alert()
    assert alert is not None
    assert len(world.browser.windows) == 1


def check_no_alert():
    """
    Make sure the alert has gone away.

    Note that the splinter documentation indicates that
    get_alert should return None if no alert is present,
    however that is not the case. Instead a
    NoAlertPresentException is raised.
    """
    try:
        assert_is_none(world.browser.get_alert())
    except NoAlertPresentException:
        pass


@step('I accept the permission alert and view the LTI$')
def accept_lti_permission_alert(_step):
    parent_window = world.browser.current_window  # Save the parent window

    # To start with you should only have one window/tab
    assert len(world.browser.windows) == 1
    alert = world.browser.get_alert()
    alert.accept()
    check_no_alert()

    # Give it a few seconds for the LTI window to appear
    world.wait_for(
        lambda _: len(world.browser.windows) == 2,
        timeout=5,
        timeout_msg="Timed out waiting for the LTI window to appear."
    )

    # Verify the LTI window
    check_lti_popup(parent_window)


@step('I reject the permission alert and do not view the LTI$')
def reject_lti_permission_alert(_step):
    alert = world.browser.get_alert()
    alert.dismiss()
    check_no_alert()
    assert len(world.browser.windows) == 1


@step('I view the LTI but incorrect_signature warning is rendered$')
def incorrect_lti_is_rendered(_step):
    assert world.is_css_present('iframe', wait_time=2)
    assert not world.is_css_present('.link_lti_new_window', wait_time=0)
    assert not world.is_css_present('.error_message', wait_time=0)

    # inside iframe test content is presented
    check_lti_iframe_content("Wrong LTI signature")


@step('the course has correct LTI credentials with registered (.*)$')
def set_correct_lti_passport(_step, user='Instructor'):
    coursenum = TEST_COURSE_NAME
    metadata = {
        'lti_passports': ["correct_lti_id:test_client_key:test_client_secret"]
    }

    i_am_registered_for_the_course(coursenum, metadata, user)


@step('the course has incorrect LTI credentials$')
def set_incorrect_lti_passport(_step):
    coursenum = TEST_COURSE_NAME
    metadata = {
        'lti_passports': ["test_lti_id:test_client_key:incorrect_lti_secret_key"]
    }

    i_am_registered_for_the_course(coursenum, metadata)


@step(r'the course has an LTI component with (.*) fields(?:\:)?$')  # , new_page is(.*), graded is(.*)
def add_correct_lti_to_course(_step, fields):
    category = 'lti'
    metadata = {
        'lti_id': 'correct_lti_id',
        'launch_url': 'http://127.0.0.1:{}/correct_lti_endpoint'.format(settings.LTI_PORT),
    }

    if fields.strip() == 'incorrect_lti_id':  # incorrect fields
        metadata.update({
            'lti_id': 'incorrect_lti_id'
        })
    elif fields.strip() == 'correct':  # correct fields
        pass
    elif fields.strip() == 'no_launch_url':
        metadata.update({
            'launch_url': u''
        })
    else:  # incorrect parameter
        assert False

    if _step.hashes:
        metadata.update(_step.hashes[0])

    world.scenario_dict['LTI'] = world.ItemFactory.create(
        parent_location=world.scenario_dict['SECTION'].location,
        category=category,
        display_name='LTI',
        metadata=metadata,
    )

    world.scenario_dict['LTI'].TEST_BASE_PATH = '{host}:{port}'.format(
        host=world.browser.host,
        port=world.browser.port,
    )

    visit_scenario_item('LTI')


def create_course_for_lti(course, metadata):
    # First clear the modulestore so we don't try to recreate
    # the same course twice
    # This also ensures that the necessary templates are loaded
    world.clear_courses()

    weight = 0.1
    grading_policy = {
        "GRADER": [
            {
                "type": "Homework",
                "min_count": 1,
                "drop_count": 0,
                "short_label": "HW",
                "weight": weight
            },
        ]
    }

    # Create the course
    # We always use the same org and display name,
    # but vary the course identifier (e.g. 600x or 191x)
    world.scenario_dict['COURSE'] = world.CourseFactory.create(
        org='edx',
        number=course,
        display_name='Test Course',
        metadata=metadata,
        grading_policy=grading_policy,
    )

    # Add a section to the course to contain problems
    world.scenario_dict['CHAPTER'] = world.ItemFactory.create(
        parent_location=world.scenario_dict['COURSE'].location,
        category='chapter',
        display_name='Test Chapter',
    )
    world.scenario_dict['SECTION'] = world.ItemFactory.create(
        parent_location=world.scenario_dict['CHAPTER'].location,
        category='sequential',
        display_name='Test Section',
        metadata={'graded': True, 'format': 'Homework'})


@patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
def i_am_registered_for_the_course(coursenum, metadata, user='Instructor'):
    # Create user
    if user == 'BetaTester':
        # Create the course
        now = datetime.datetime.now(pytz.UTC)
        tomorrow = now + datetime.timedelta(days=5)
        metadata.update({'days_early_for_beta': 5, 'start': tomorrow})
        create_course_for_lti(coursenum, metadata)
        course_descriptor = world.scenario_dict['COURSE']

        # create beta tester
        user = BetaTesterFactory(course_key=course_descriptor.id)
        normal_student = UserFactory()
        instructor = InstructorFactory(course_key=course_descriptor.id)

        assert not has_access(normal_student, 'load', course_descriptor)
        assert has_access(user, 'load', course_descriptor)
        assert has_access(instructor, 'load', course_descriptor)
    else:
        metadata.update({'start': datetime.datetime(1970, 1, 1, tzinfo=UTC)})
        create_course_for_lti(coursenum, metadata)
        course_descriptor = world.scenario_dict['COURSE']
        user = InstructorFactory(course_key=course_descriptor.id)

    # Enroll the user in the course and log them in
    if has_access(user, 'load', course_descriptor):
        world.enroll_user(user, course_descriptor.id)

    world.log_in(username=user.username, password='test')


def check_lti_popup(parent_window):
    # You should now have 2 browser windows open, the original courseware and the LTI
    windows = world.browser.windows
    assert_equal(len(windows), 2)

    # For verification, iterate through the window titles and make sure that
    # both are there.
    tabs = []
    expected_tabs = [
        u'LTI | Test Section | {course} Courseware | {platform}'.format(
            course=TEST_COURSE_NAME,
            platform=settings.PLATFORM_NAME
        ),
        u'TEST TITLE'
    ]

    for window in windows:
        world.browser.switch_to_window(window)
        tabs.append(world.browser.title)
    assert_equal(tabs, expected_tabs)

    # Now verify the contents of the LTI window (which is the 2nd window/tab)
    # Note: The LTI opens in a new browser window, but Selenium sticks with the
    # current window until you explicitly switch to the context of the new one.
    world.browser.switch_to_window(windows[1])
    url = world.browser.url
    basename = os.path.basename(url)
    pathname = os.path.splitext(basename)[0]
    assert_equal(pathname, u'correct_lti_endpoint')

    result = world.css_find('.result').first.text
    assert_equal(result, u'This is LTI tool. Success.')

    world.browser.driver.close()  # Close the pop-up window
    world.browser.switch_to_window(parent_window)  # Switch to the main window again


def click_and_check_lti_popup():
    parent_window = world.browser.current_window  # Save the parent window
    world.css_find('.link_lti_new_window').first.click()
    check_lti_popup(parent_window)


@step('visit the LTI component')
def visit_lti_component(_step):
    visit_scenario_item('LTI')


@step('I see LTI component (.*) with text "([^"]*)"$')
def see_elem_text(_step, elem, text):
    selector_map = {
        'progress': '.problem-progress',
        'feedback': '.problem-feedback',
        'module title': '.problem-header',
        'button': '.link_lti_new_window',
        'description': '.lti-description'
    }
    assert_in(elem, selector_map)
    assert_true(world.css_has_text(selector_map[elem], text))


@step('I see text "([^"]*)"$')
def check_progress(_step, text):
    assert world.browser.is_text_present(text)


@step('I see graph with total progress "([^"]*)"$')
def see_graph(_step, progress):
    assert_equal(progress, world.css_find('#grade-detail-graph .overallGrade').first.text.split('\n')[1])


@step('I see in the gradebook table that "([^"]*)" is "([^"]*)"$')
def see_value_in_the_gradebook(_step, label, text):
    table_selector = '.grade-table'
    index = 0
    table_headers = world.css_find('{0} thead th'.format(table_selector))

    for i, element in enumerate(table_headers):
        if element.text.strip() == label:
            index = i
            break

    assert_true(world.css_has_text('{0} tbody td'.format(table_selector), text, index=index))


@step('I submit answer to LTI (.*) question$')
def click_grade(_step, version):
    version_map = {
        '1': {'selector': 'submit-button', 'expected_text': 'LTI consumer (edX) responded with XML content'},
        '2': {'selector': 'submit-lti2-button', 'expected_text': 'LTI consumer (edX) responded with HTTP 200'},
    }
    assert_in(version, version_map)
    location = world.scenario_dict['LTI'].location.html_id()
    iframe_name = 'ltiFrame-' + location
    with world.browser.get_iframe(iframe_name) as iframe:
        css_ele = version_map[version]['selector']
        css_loc = '#' + css_ele
        world.wait_for_visible(css_loc)
        world.css_click(css_loc)
        assert iframe.is_text_present(version_map[version]['expected_text'])


@step('LTI provider deletes my grade and feedback$')
def click_delete_button(_step):
    with world.browser.get_iframe(get_lti_frame_name()) as iframe:
        iframe.find_by_name('submit-lti2-delete-button').first.click()


def get_lti_frame_name():
    location = world.scenario_dict['LTI'].location.html_id()
    return 'ltiFrame-' + location


@step('I see in iframe that LTI role is (.*)$')
def check_role(_step, role):
    world.wait_for_present('iframe')
    location = world.scenario_dict['LTI'].location.html_id()
    iframe_name = 'ltiFrame-' + location
    with world.browser.get_iframe(iframe_name) as iframe:
        expected_role = 'Role: ' + role
        role = world.retry_on_exception(
            lambda: iframe.find_by_tag('h5').first.value,
            max_attempts=5,
            ignored_exceptions=ElementDoesNotExist
        )
        assert_equal(expected_role, role)


@step('I switch to (.*)$')
def switch_view(_step, view):
    staff_status = world.css_find('#action-preview-select').first.value
    if staff_status != view:
        world.browser.select("select", view)
        world.wait_for_ajax_complete()
        assert_equal(world.css_find('#action-preview-select').first.value, view)


@step("in the LTI component I do not see (.*)$")
def check_lti_component_no_elem(_step, text):
    selector_map = {
        'a launch button': '.link_lti_new_window',
        'an provider iframe': '.ltiLaunchFrame',
        'feedback': '.problem-feedback',
        'progress': '.problem-progress',
    }
    assert_in(text, selector_map)
    assert_true(world.is_css_not_present(selector_map[text]))
