"""Tests for QTI XML parsing utilities."""

from timeback_qti.parse import (
    QtiChoice,
    extract_choices,
    extract_correct_response,
    extract_feedback,
    extract_interaction_attributes,
    extract_modal_feedback,
    extract_prompt,
    extract_response_declaration,
    parse_qti_xml,
)

EXAMPLE_XML = """\
<?xml version="1.0" encoding="UTF-8"?>
<qti-assessment-item
  xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0"
  identifier="example-q1"
  title="Example Question"
  adaptive="false"
  time-dependent="false">

  <qti-response-declaration identifier="RESPONSE" cardinality="single" base-type="identifier">
    <qti-correct-response>
      <qti-value>B</qti-value>
    </qti-correct-response>
  </qti-response-declaration>

  <qti-outcome-declaration identifier="FEEDBACK" cardinality="single" base-type="identifier"/>
  <qti-outcome-declaration identifier="FEEDBACK-INLINE" cardinality="single" base-type="identifier"/>

  <qti-item-body>
    <qti-choice-interaction response-identifier="RESPONSE" shuffle="false" max-choices="1">
      <qti-prompt>What is 2 + 2?</qti-prompt>
      <qti-simple-choice identifier="A">
        3
        <qti-feedback-inline outcome-identifier="FEEDBACK-INLINE" identifier="A" show-hide="show">
          <span style="color: #D9534F;">Incorrect: Try counting again.</span>
        </qti-feedback-inline>
      </qti-simple-choice>
      <qti-simple-choice identifier="B">
        4
        <qti-feedback-inline outcome-identifier="FEEDBACK-INLINE" identifier="B" show-hide="show">
          <span style="color: #2E8B57;">Correct: Well done!</span>
        </qti-feedback-inline>
      </qti-simple-choice>
      <qti-simple-choice identifier="C">
        5
        <qti-feedback-inline outcome-identifier="FEEDBACK-INLINE" identifier="C" show-hide="show">
          <span style="color: #D9534F;">Incorrect: Try counting again.</span>
        </qti-feedback-inline>
      </qti-simple-choice>
      <qti-simple-choice identifier="D">
        6
        <qti-feedback-inline outcome-identifier="FEEDBACK-INLINE" identifier="D" show-hide="show">
          <span style="color: #D9534F;">Incorrect: Try counting again.</span>
        </qti-feedback-inline>
      </qti-simple-choice>
    </qti-choice-interaction>
  </qti-item-body>

  <qti-modal-feedback outcome-identifier="FEEDBACK" identifier="CORRECT" show-hide="show">
    <qti-content-body>
      <p><strong>Correct!</strong> Well done.</p>
    </qti-content-body>
  </qti-modal-feedback>

  <qti-modal-feedback outcome-identifier="FEEDBACK" identifier="INCORRECT" show-hide="show">
    <qti-content-body>
      <p><strong>Incorrect.</strong> Please review and try again.</p>
    </qti-content-body>
  </qti-modal-feedback>
</qti-assessment-item>"""

MINIMAL_XML = """\
<qti-item-body>
  <qti-choice-interaction response-identifier="R1" shuffle="true" max-choices="2">
    <qti-prompt>Pick the prime numbers.</qti-prompt>
    <qti-simple-choice identifier="X">2</qti-simple-choice>
    <qti-simple-choice identifier="Y">4</qti-simple-choice>
  </qti-choice-interaction>
</qti-item-body>"""


class TestExtractPrompt:
    """Tests for extract_prompt."""

    def test_extracts_the_prompt_text(self):
        assert extract_prompt(EXAMPLE_XML) == "What is 2 + 2?"

    def test_handles_html_content_inside_prompts(self):
        xml = "<qti-prompt>What is <em>2 + 2</em>?</qti-prompt>"
        assert extract_prompt(xml) == "What is <em>2 + 2</em>?"

    def test_returns_empty_string_for_missing_prompt(self):
        assert extract_prompt("<qti-item-body></qti-item-body>") == ""

    def test_returns_empty_string_for_empty_input(self):
        assert extract_prompt("") == ""


class TestExtractChoices:
    """Tests for extract_choices."""

    def test_extracts_all_choices_with_identifiers(self):
        choices = extract_choices(EXAMPLE_XML)
        assert len(choices) == 4
        assert choices[0].identifier == "A"
        assert choices[0].text == "3"
        assert choices[1].identifier == "B"
        assert choices[1].text == "4"
        assert choices[2].identifier == "C"
        assert choices[2].text == "5"
        assert choices[3].identifier == "D"
        assert choices[3].text == "6"

    def test_strips_inline_feedback_from_choice_text(self):
        choices = extract_choices(EXAMPLE_XML)
        for choice in choices:
            assert "qti-feedback-inline" not in choice.text
            assert "Incorrect" not in choice.text
            assert "Correct" not in choice.text

    def test_handles_choices_without_inline_feedback(self):
        choices = extract_choices(MINIMAL_XML)
        assert len(choices) == 2
        assert choices[0].identifier == "X"
        assert choices[0].text == "2"
        assert choices[1].identifier == "Y"
        assert choices[1].text == "4"

    def test_returns_empty_list_for_missing_choices(self):
        assert extract_choices("<qti-item-body></qti-item-body>") == []

    def test_returns_empty_list_for_empty_input(self):
        assert extract_choices("") == []

    def test_captures_identifier_not_template_identifier_when_compound_attributes_are_present(
        self,
    ):
        xml = (
            '<qti-simple-choice identifier="A" template-identifier="tpl1">Option A</qti-simple-choice>'
            '<qti-simple-choice template-identifier="tpl2" identifier="B">Option B</qti-simple-choice>'
        )
        choices = extract_choices(xml)
        assert choices == [
            QtiChoice(identifier="A", text="Option A"),
            QtiChoice(identifier="B", text="Option B"),
        ]


class TestExtractCorrectResponse:
    """Tests for extract_correct_response."""

    def test_extracts_the_correct_answer_identifier(self):
        assert extract_correct_response(EXAMPLE_XML) == "B"

    def test_returns_none_when_no_correct_response_exists(self):
        assert extract_correct_response(MINIMAL_XML) is None

    def test_returns_none_for_empty_input(self):
        assert extract_correct_response("") is None


class TestExtractFeedback:
    """Tests for extract_feedback."""

    def test_extracts_all_inline_feedback_entries(self):
        feedback = extract_feedback(EXAMPLE_XML)
        assert len(feedback) == 4

    def test_captures_outcome_identifier_identifier_and_show_hide(self):
        feedback = extract_feedback(EXAMPLE_XML)
        assert feedback[0].outcome_identifier == "FEEDBACK-INLINE"
        assert feedback[0].identifier == "A"
        assert feedback[0].show_hide == "show"

    def test_captures_feedback_content(self):
        feedback = extract_feedback(EXAMPLE_XML)
        assert "Incorrect: Try counting again." in feedback[0].content
        assert "Correct: Well done!" in feedback[1].content

    def test_correctly_parses_show_hide_hide(self):
        xml = '<qti-feedback-inline outcome-identifier="FB" identifier="X" show-hide="hide">Hidden feedback</qti-feedback-inline>'
        feedback = extract_feedback(xml)
        assert len(feedback) == 1
        assert feedback[0].show_hide == "hide"

    def test_returns_empty_list_when_no_feedback_exists(self):
        assert extract_feedback(MINIMAL_XML) == []

    def test_returns_empty_list_for_empty_input(self):
        assert extract_feedback("") == []


class TestExtractModalFeedback:
    """Tests for extract_modal_feedback."""

    def test_extracts_all_modal_feedback_entries(self):
        modal = extract_modal_feedback(EXAMPLE_XML)
        assert len(modal) == 2

    def test_extracts_content_from_qti_content_body(self):
        modal = extract_modal_feedback(EXAMPLE_XML)
        assert modal[0].outcome_identifier == "FEEDBACK"
        assert modal[0].identifier == "CORRECT"
        assert modal[0].show_hide == "show"
        assert "<strong>Correct!</strong>" in modal[0].content

    def test_extracts_incorrect_feedback(self):
        modal = extract_modal_feedback(EXAMPLE_XML)
        assert modal[1].identifier == "INCORRECT"
        assert "Please review and try again." in modal[1].content

    def test_correctly_parses_show_hide_hide(self):
        xml = '<qti-modal-feedback outcome-identifier="FB" identifier="Y" show-hide="hide"><qti-content-body>Hidden</qti-content-body></qti-modal-feedback>'
        modal = extract_modal_feedback(xml)
        assert len(modal) == 1
        assert modal[0].show_hide == "hide"

    def test_returns_empty_list_when_no_modal_feedback_exists(self):
        assert extract_modal_feedback(MINIMAL_XML) == []

    def test_returns_empty_list_for_empty_input(self):
        assert extract_modal_feedback("") == []


class TestExtractInteractionAttributes:
    """Tests for extract_interaction_attributes."""

    def test_extracts_interaction_attributes(self):
        attrs = extract_interaction_attributes(EXAMPLE_XML)
        assert attrs is not None
        assert attrs.response_identifier == "RESPONSE"
        assert attrs.shuffle is False
        assert attrs.max_choices == 1

    def test_parses_shuffle_true(self):
        attrs = extract_interaction_attributes(MINIMAL_XML)
        assert attrs is not None
        assert attrs.response_identifier == "R1"
        assert attrs.shuffle is True
        assert attrs.max_choices == 2

    def test_returns_none_when_no_choice_interaction_exists(self):
        assert extract_interaction_attributes("<qti-item-body></qti-item-body>") is None

    def test_returns_none_for_empty_input(self):
        assert extract_interaction_attributes("") is None


class TestExtractResponseDeclaration:
    """Tests for extract_response_declaration."""

    def test_extracts_response_declaration(self):
        decl = extract_response_declaration(EXAMPLE_XML)
        assert decl is not None
        assert decl.identifier == "RESPONSE"
        assert decl.cardinality == "single"
        assert decl.base_type == "identifier"

    def test_returns_none_when_no_response_declaration_exists(self):
        assert extract_response_declaration(MINIMAL_XML) is None

    def test_returns_none_for_empty_input(self):
        assert extract_response_declaration("") is None


class TestParseQtiXml:
    """Tests for parse_qti_xml."""

    def test_returns_a_complete_parsed_item_from_full_xml(self):
        item = parse_qti_xml(EXAMPLE_XML)

        assert item.prompt == "What is 2 + 2?"
        assert len(item.choices) == 4
        assert item.correct_answer == "B"
        assert len(item.feedback) == 4
        assert len(item.modal_feedback) == 2
        assert item.interaction is not None
        assert item.interaction.response_identifier == "RESPONSE"
        assert item.interaction.shuffle is False
        assert item.interaction.max_choices == 1
        assert item.response_declaration is not None
        assert item.response_declaration.identifier == "RESPONSE"
        assert item.response_declaration.cardinality == "single"
        assert item.response_declaration.base_type == "identifier"

    def test_handles_minimal_xml_gracefully(self):
        item = parse_qti_xml(MINIMAL_XML)

        assert item.prompt == "Pick the prime numbers."
        assert len(item.choices) == 2
        assert item.correct_answer is None
        assert item.feedback == []
        assert item.modal_feedback == []
        assert item.interaction is not None
        assert item.interaction.response_identifier == "R1"
        assert item.interaction.shuffle is True
        assert item.interaction.max_choices == 2
        assert item.response_declaration is None

    def test_handles_empty_string(self):
        item = parse_qti_xml("")

        assert item.prompt == ""
        assert item.choices == []
        assert item.correct_answer is None
        assert item.feedback == []
        assert item.modal_feedback == []
        assert item.interaction is None
        assert item.response_declaration is None
