"""
Test capa problem.
"""
import ddt
import textwrap
from lxml import etree
import unittest

from capa.tests.helpers import new_loncapa_problem


@ddt.ddt
class CAPAProblemTest(unittest.TestCase):
    """ CAPA problem related tests"""

    @ddt.unpack
    @ddt.data(
        {'question': 'Select the correct synonym of paranoid?'},
        {'question': 'Select the correct <em>synonym</em> of <strong>paranoid</strong>?'},
    )
    def test_label_and_description_inside_responsetype(self, question):
        """
        Verify that
        * label is extracted
        * <label> tag is removed to avoid duplication

        This is the case when we have a problem with single question or
        problem with multiple-questions separated as per the new format.
        """
        xml = """
        <problem>
            <choiceresponse>
                <label>{question}</label>
                <description>Only the paranoid survive.</description>
                <checkboxgroup>
                    <choice correct="true">over-suspicious</choice>
                    <choice correct="false">funny</choice>
                </checkboxgroup>
            </choiceresponse>
        </problem>
        """.format(question=question)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': question,
                    'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
                }
            }
        )
        self.assertEqual(len(problem.tree.xpath('//label')), 0)

    @ddt.unpack
    @ddt.data(
        {
            'question': 'Once we become predictable, we become ______?',
            'label_attr': 'Once we become predictable, we become ______?'
        },
        {
            'question': 'Once we become predictable, we become ______?<img src="img/src"/>',
            'label_attr': 'Once we become predictable, we become ______?'
        },
    )
    def test_legacy_problem(self, question, label_attr):
        """
        Verify that legacy problem is handled correctly.
        """
        xml = """
        <problem>
            <p>Be sure to check your spelling.</p>
            <p>{}</p>
            <stringresponse answer="vulnerable" type="ci">
                <textline label="{}" size="40"/>
            </stringresponse>
        </problem>
        """.format(question, label_attr)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': question,
                    'descriptions': {}
                }
            }
        )
        self.assertEqual(
            len(problem.tree.xpath("//*[normalize-space(text())='{}']".format(question))),
            0
        )

    @ddt.unpack
    @ddt.data(
        {
            'question1': 'People who say they have nothing to ____ almost always do?',
            'question2': 'Select the correct synonym of paranoid?'
        },
        {
            'question1': '<b>People</b> who say they have <mark>nothing</mark> to ____ almost always do?',
            'question2': 'Select the <sup>correct</sup> synonym of <mark>paranoid</mark>?'
        },
    )
    def test_neither_label_tag_nor_attribute(self, question1, question2):
        """
        Verify that label is extracted correctly.

        This is the case when we have a markdown problem with multiple-questions.
        In this case when markdown is converted to xml, there will be no label
        tag and label attribute inside responsetype. But we have a label tag
        before the responsetype.
        """
        xml = """
        <problem>
            <p>Be sure to check your spelling.</p>
            <label>{}</label>
            <stringresponse answer="hide" type="ci">
                <textline size="40"/>
            </stringresponse>
            <choiceresponse>
                <label>{}</label>
                <checkboxgroup>
                    <choice correct="true">over-suspicious</choice>
                    <choice correct="false">funny</choice>
                </checkboxgroup>
            </choiceresponse>
        </problem>
        """.format(question1, question2)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': question1,
                    'descriptions': {}
                },
                '1_3_1':
                {
                    'label': question2,
                    'descriptions': {}
                }
            }

        )
        for question in (question1, question2):
            self.assertEqual(
                len(problem.tree.xpath('//label[text()="{}"]'.format(question))),
                0
            )

    def test_multiple_descriptions(self):
        """
        Verify that multiple descriptions are handled correctly.
        """
        desc1 = "The problem with trying to be the <em>bad guy</em>, there's always someone <strong>worse</strong>."
        desc2 = "Anyone who looks the world as if it was a game of chess deserves to lose."
        xml = """
        <problem>
            <p>Be sure to check your spelling.</p>
            <stringresponse answer="War" type="ci">
                <label>___ requires sacrifices.</label>
                <description>{}</description>
                <description>{}</description>
                <textline size="40"/>
            </stringresponse>
        </problem>
        """.format(desc1, desc2)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': '___ requires sacrifices.',
                    'descriptions': {
                        'description_1_1_1': desc1,
                        'description_1_1_2': desc2
                    }
                }
            }
        )

    def test_non_accessible_inputtype(self):
        """
        Verify that tag with question text is not removed when inputtype is not fully accessible.
        """
        question = "Click the country which is home to the Pyramids."
        xml = """
        <problem>
            <p>{}</p>
            <imageresponse>
                <imageinput label="{}"
                src="/static/Africa.png" width="600" height="638" rectangle="(338,98)-(412,168)"/>
            </imageresponse>
        </problem>
        """.format(question, question)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': question,
                    'descriptions': {}
                }
            }
        )
        # <p> tag with question text should not be deleted
        self.assertEqual(problem.tree.xpath("string(p[text()='{}'])".format(question)), question)

    def test_label_is_empty_if_no_label_attribute(self):
        """
        Verify that label in response_data is empty string when label
        attribute is missing and responsetype is not fully accessible.
        """
        question = "Click the country which is home to the Pyramids."
        xml = """
        <problem>
            <p>{}</p>
            <imageresponse>
                <imageinput
                src="/static/Africa.png" width="600" height="638" rectangle="(338,98)-(412,168)"/>
            </imageresponse>
        </problem>
        """.format(question)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': '',
                    'descriptions': {}
                }
            }
        )

    def test_multiple_questions_problem(self):
        """
        For a problem with multiple questions verify that for each question
        * label is extracted
        * descriptions info is constructed
        * <label> tag is removed to avoid duplication
        """
        xml = """
        <problem>
            <choiceresponse>
                <label>Select the correct synonym of paranoid?</label>
                <description>Only the paranoid survive.</description>
                <checkboxgroup>
                    <choice correct="true">over-suspicious</choice>
                    <choice correct="false">funny</choice>
                </checkboxgroup>
            </choiceresponse>
            <multiplechoiceresponse>
                <p>one more question</p>
                <label>What Apple device competed with the portable CD player?</label>
                <description>Device looks like an egg plant.</description>
                <choicegroup type="MultipleChoice">
                    <choice correct="false">The iPad</choice>
                    <choice correct="false">Napster</choice>
                    <choice correct="true">The iPod</choice>
                    <choice correct="false">The vegetable peeler</choice>
                </choicegroup>
            </multiplechoiceresponse>
        </problem>
        """
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': 'Select the correct synonym of paranoid?',
                    'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
                },
                '1_3_1':
                {
                    'label': 'What Apple device competed with the portable CD player?',
                    'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}
                }
            }
        )
        self.assertEqual(len(problem.tree.xpath('//label')), 0)

    def test_question_title_not_removed_got_children(self):
        """
        Verify that <p> question text before responsetype not deleted when
        it contains other children and label is picked from label attribute of inputtype

        This is the case when author updated the <p> immediately before
        responsetype to contain other elements. We do not want to delete information in that case.
        """
        question = 'Is egg plant a fruit?'
        xml = """
        <problem>
            <p>Choose wisely.</p>
            <p>Select the correct synonym of paranoid?</p>
            <p><img src="" /></p>
            <choiceresponse>
                <checkboxgroup label="{}">
                    <choice correct="true">over-suspicious</choice>
                    <choice correct="false">funny</choice>
                </checkboxgroup>
            </choiceresponse>
        </problem>
        """.format(question)
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': '',
                    'descriptions': {}
                }
            }
        )
        self.assertEqual(
            len(problem.tree.xpath('//p/img')),
            1
        )

    @ddt.unpack
    @ddt.data(
        {'group_label': 'Choose the correct color'},
        {'group_label': 'Choose the <b>correct</b> <mark>color</mark>'},
    )
    def test_multiple_inputtypes(self, group_label):
        """
        Verify that group label and labels for individual inputtypes are extracted correctly.
        """
        input1_label = 'What color is the sky?'
        input2_label = 'What color are pine needles?'
        xml = """
        <problem>
            <optionresponse>
                <label>{}</label>
                <optioninput options="('yellow','blue','green')" correct="blue" label="{}"/>
                <optioninput options="('yellow','blue','green')" correct="green" label="{}"/>
            </optionresponse>
        </problem>
        """.format(group_label, input1_label, input2_label)

        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'group_label': group_label,
                    'label': input1_label,
                    'descriptions': {}
                },
                '1_2_2':
                {
                    'group_label': group_label,
                    'label': input2_label,
                    'descriptions': {}
                }
            }
        )

    def test_single_inputtypes(self):
        """
        Verify that HTML is correctly rendered when there is single inputtype.
        """
        question = 'Enter sum of 1+2'
        xml = textwrap.dedent("""
        <problem>
            <customresponse cfn="test_sum" expect="3">
        <script type="loncapa/python">
        def test_sum(expect, ans):
            return int(expect) == int(ans)
        </script>
                <label>{}</label>
                <textline size="20" correct_answer="3" />
            </customresponse>
        </problem>
        """.format(question))
        problem = new_loncapa_problem(xml, use_capa_render_template=True)
        problem_html = etree.XML(problem.get_html())

        # verify that only no multi input group div is present
        multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')
        self.assertEqual(len(multi_inputs_group), 0)

        # verify that question is rendered only once
        question = problem_html.xpath("//*[normalize-space(text())='{}']".format(question))
        self.assertEqual(len(question), 1)

    def assert_question_tag(self, question1, question2, tag, label_attr=False):
        """
        Verify question tag correctness.
        """
        question1_tag = '<{tag}>{}</{tag}>'.format(question1, tag=tag) if question1 else ''
        question2_tag = '<{tag}>{}</{tag}>'.format(question2, tag=tag) if question2 else ''
        question1_label_attr = 'label="{}"'.format(question1) if label_attr else ''
        question2_label_attr = 'label="{}"'.format(question2) if label_attr else ''
        xml = """
        <problem>
            {question1_tag}
            <choiceresponse>
                <checkboxgroup {question1_label_attr}>
                    <choice correct="true">choice1</choice>
                    <choice correct="false">choice2</choice>
                </checkboxgroup>
            </choiceresponse>
            {question2_tag}
            <multiplechoiceresponse>
                <choicegroup type="MultipleChoice" {question2_label_attr}>
                    <choice correct="false">choice1</choice>
                    <choice correct="true">choice2</choice>
                </choicegroup>
            </multiplechoiceresponse>
        </problem>
        """.format(
            question1_tag=question1_tag,
            question2_tag=question2_tag,
            question1_label_attr=question1_label_attr,
            question2_label_attr=question2_label_attr,
        )
        problem = new_loncapa_problem(xml)
        self.assertEqual(
            problem.problem_data,
            {
                '1_2_1':
                {
                    'label': question1,
                    'descriptions': {}
                },
                '1_3_1':
                {
                    'label': question2,
                    'descriptions': {}
                }
            }
        )
        self.assertEqual(len(problem.tree.xpath('//{}'.format(tag))), 0)

    @ddt.unpack
    @ddt.data(
        {'question1': 'question 1 label', 'question2': 'question 2 label'},
        {'question1': '', 'question2': 'question 2 label'},
        {'question1': 'question 1 label', 'question2': ''}
    )
    def test_correct_question_tag_is_picked(self, question1, question2):
        """
        For a problem with multiple questions verify that correct question tag is picked.
        """
        self.assert_question_tag(question1, question2, tag='label', label_attr=False)
        self.assert_question_tag(question1, question2, tag='p', label_attr=True)


@ddt.ddt
class CAPAMultiInputProblemTest(unittest.TestCase):
    """ TestCase for CAPA problems with multiple inputtypes """

    def capa_problem(self, xml):
        """
        Create capa problem.
        """
        return new_loncapa_problem(xml, use_capa_render_template=True)

    def assert_problem_html(self, problme_html, group_label, *input_labels):
        """
        Verify that correct html is rendered for multiple inputtypes.

        Arguments:
            problme_html (str): problem HTML
            group_label (str or None): multi input group label or None if label is not present
            input_labels (tuple): individual input labels
        """
        html = etree.XML(problme_html)

        # verify that only one multi input group div is present at correct path
        multi_inputs_group = html.xpath(
            '//div[@class="wrapper-problem-response"]/div[@class="multi-inputs-group"]'
        )
        self.assertEqual(len(multi_inputs_group), 1)

        if group_label is None:
            # if multi inputs group label is not present then there shouldn't be `aria-labelledby` attribute
            self.assertEqual(multi_inputs_group[0].attrib.get('aria-labelledby'), None)
        else:
            # verify that multi input group label <p> tag exists and its
            # id matches with correct multi input group aria-labelledby
            multi_inputs_group_label_id = multi_inputs_group[0].attrib.get('aria-labelledby')
            multi_inputs_group_label = html.xpath('//p[@id="{}"]'.format(multi_inputs_group_label_id))
            self.assertEqual(len(multi_inputs_group_label), 1)
            self.assertEqual(multi_inputs_group_label[0].text, group_label)

        # verify that label for each input comes only once
        for input_label in input_labels:
            # normalize-space is used to remove whitespace around the text
            input_label_element = multi_inputs_group[0].xpath('//*[normalize-space(text())="{}"]'.format(input_label))
            self.assertEqual(len(input_label_element), 1)

    @ddt.unpack
    @ddt.data(
        {'label_html': '<label>Choose the correct color</label>', 'group_label': 'Choose the correct color'},
        {'label_html': '', 'group_label': None}
    )
    def test_optionresponse(self, label_html, group_label):
        """
        Verify that optionresponse problem with multiple inputtypes is rendered correctly.
        """
        input1_label = 'What color is the sky?'
        input2_label = 'What color are pine needles?'
        xml = """
        <problem>
            <optionresponse>
                {label_html}
                <optioninput options="('yellow','blue','green')" correct="blue" label="{input1_label}"/>
                <optioninput options="('yellow','blue','green')" correct="green" label="{input2_label}"/>
            </optionresponse>
        </problem>
        """.format(label_html=label_html, input1_label=input1_label, input2_label=input2_label)
        problem = self.capa_problem(xml)
        self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)

    @ddt.unpack
    @ddt.data(
        {'inputtype': 'textline'},
        {'inputtype': 'formulaequationinput'}
    )
    def test_customresponse(self, inputtype):
        """
        Verify that customresponse problem with multiple textline
        and formulaequationinput inputtypes is rendered correctly.
        """
        group_label = 'Enter two integers that sum to 10.'
        input1_label = 'Integer 1'
        input2_label = 'Integer 2'
        xml = textwrap.dedent("""
        <problem>
            <customresponse cfn="test_add_to_ten">
        <script type="loncapa/python">
        def test_add_to_ten(expect, ans):
            return test_add(10, ans)
        </script>
                <label>{}</label>
                <{inputtype} size="40" correct_answer="3" label="{}" /><br/>
                <{inputtype} size="40" correct_answer="7" label="{}" />
            </customresponse>
        </problem>
        """.format(group_label, input1_label, input2_label, inputtype=inputtype))
        problem = self.capa_problem(xml)
        self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)

    @ddt.unpack
    @ddt.data(
        {
            'descriptions': ('desc1', 'desc2'),
            'descriptions_html': '<description>desc1</description><description>desc2</description>'
        },
        {
            'descriptions': (),
            'descriptions_html': ''
        }
    )
    def test_descriptions(self, descriptions, descriptions_html):
        """
        Verify that groups descriptions are rendered correctly.
        """
        xml = """
        <problem>
            <optionresponse>
                <label>group label</label>
                {descriptions_html}
                <optioninput options="('yellow','blue','green')" correct="blue" label="first label"/>
                <optioninput options="('yellow','blue','green')" correct="green" label="second label"/>
            </optionresponse>
        </problem>
        """.format(descriptions_html=descriptions_html)
        problem = self.capa_problem(xml)
        problem_html = etree.XML(problem.get_html())

        multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')[0]
        description_ids = multi_inputs_group.attrib.get('aria-describedby', '').split()

        # Verify that number of descriptions matches description_ids
        self.assertEqual(len(description_ids), len(descriptions))

        # For each description, check its order and text is correct
        for index, description_id in enumerate(description_ids):
            description_element = multi_inputs_group.xpath('//p[@id="{}"]'.format(description_id))
            self.assertEqual(len(description_element), 1)
            self.assertEqual(description_element[0].text, descriptions[index])
