"""
quizlib Moodle XML format renderer
"""

import re
import html
from typing import TYPE_CHECKING
import xml.etree.ElementTree as ET

from quizlib.questions.cloze import Cloze
from quizlib.questions.essay import Essay
from quizlib.questions.multiple_choice import MultipleChoice
from quizlib.questions.true_false import TrueFalse

if TYPE_CHECKING:
    from quizlib.questions.base import Question
    from quizlib.quiz import Quiz


class MoodleXMLRenderer:
    """Renders quizzes to Moodle XML format."""

    def render(self, quiz: "Quiz") -> str:
        """Render a complete quiz to Moodle XML."""
        root = ET.Element("quiz")

        q_cat = ET.SubElement(root, "question", type="category")
        cat = ET.SubElement(q_cat, "category")
        ET.SubElement(cat, "text").text = f"$course$/top/{quiz.category}"

        info = ET.SubElement(q_cat, "info", format="moodle_auto_format")
        ET.SubElement(info, "text").text = quiz.info
        ET.SubElement(q_cat, "idnumber")

        for question in quiz.questions:
            self._render_question(root, question)

        ET.indent(root, space="  ", level=0)
        xml_str = ET.tostring(root, encoding="unicode", xml_declaration=True)
        return self._post_process_cdata(xml_str)

    def _post_process_cdata(self, xml_str: str) -> str:
        """Wrap code blocks in answers with CDATA."""
        pattern = re.compile(r"(<answer[^>]*>\s*<text>)(.*?)(</text>)", re.DOTALL)

        def replacement(match):
            prefix = match.group(1)
            body = match.group(2)
            suffix = match.group(3)

            if "```" in body:
                unescaped_body = html.unescape(body)
                return f"{prefix}<![CDATA[{unescaped_body}]]>{suffix}"
            return match.group(0)

        return pattern.sub(replacement, xml_str)

    def _render_question(self, parent: ET.Element, question: "Question") -> None:
        """Render a single question."""
        match question:
            case Essay():
                self._render_essay(parent, question)
            case MultipleChoice():
                self._render_mc(parent, question)
            case TrueFalse():
                self._render_tf(parent, question)
            case Cloze():
                self._render_cloze(parent, question)
            case _:
                raise TypeError(f"Unknown question type: {type(question)}")

    def _render_essay(self, parent: ET.Element, q: Essay) -> None:
        q_elem = ET.SubElement(parent, "question", type="essay")

        name = ET.SubElement(q_elem, "name")
        ET.SubElement(name, "text").text = q.name

        qtext = ET.SubElement(q_elem, "questiontext", format="markdown")
        ET.SubElement(qtext, "text").text = q.text

        gen_feedback = ET.SubElement(q_elem, "generalfeedback", format="markdown")
        ET.SubElement(gen_feedback, "text")

        ET.SubElement(q_elem, "defaultgrade").text = str(q.defgrade)
        ET.SubElement(q_elem, "penalty").text = "0.0000000"
        ET.SubElement(q_elem, "hidden").text = "0"
        ET.SubElement(q_elem, "idnumber")
        ET.SubElement(q_elem, "responseformat").text = "monospaced"
        ET.SubElement(q_elem, "responserequired").text = "1"
        ET.SubElement(q_elem, "responsefieldlines").text = str(q.lines)
        ET.SubElement(q_elem, "attachments").text = "0"
        ET.SubElement(q_elem, "attachmentsrequired").text = "0"
        ET.SubElement(q_elem, "maxbytes").text = "0"
        ET.SubElement(q_elem, "filetypeslist")

        graderinfo = ET.SubElement(q_elem, "graderinfo", format="markdown")
        ET.SubElement(graderinfo, "text").text = q.graderinfo

        resp_template = ET.SubElement(q_elem, "responsetemplate", format="markdown")
        ET.SubElement(resp_template, "text")

    def _render_mc(self, parent: ET.Element, q: MultipleChoice) -> None:
        q_elem = ET.SubElement(parent, "question", type="multichoice")

        name = ET.SubElement(q_elem, "name")
        ET.SubElement(name, "text").text = q.name

        qtext = ET.SubElement(q_elem, "questiontext", format="markdown")
        ET.SubElement(qtext, "text").text = q.text

        gen_feedback = ET.SubElement(
            q_elem, "generalfeedback", format="moodle_auto_format"
        )
        ET.SubElement(gen_feedback, "text")

        ET.SubElement(q_elem, "defaultgrade").text = str(q.defgrade)
        ET.SubElement(q_elem, "penalty").text = "0.0000000"
        ET.SubElement(q_elem, "hidden").text = "0"
        ET.SubElement(q_elem, "idnumber")
        ET.SubElement(q_elem, "single").text = "true" if q.is_single else "false"
        ET.SubElement(q_elem, "shuffleanswers").text = "true"
        ET.SubElement(q_elem, "answernumbering").text = "abc"
        ET.SubElement(q_elem, "showstandardinstruction").text = "0"

        for tag, text in [
            ("correctfeedback", "Die Antwort ist richtig."),
            ("partiallycorrectfeedback", "Die Antwort ist teilweise richtig."),
            ("incorrectfeedback", "Die Antwort ist falsch."),
        ]:
            fb = ET.SubElement(q_elem, tag, format="markdown")
            ET.SubElement(fb, "text").text = text

        ET.SubElement(q_elem, "shownumcorrect")

        for is_correct, answer_text in q.answers:
            fraction = f"{100 / q.n_correct:8.6f}" if is_correct == 1 else "0"
            answer = ET.SubElement(
                q_elem, "answer", fraction=fraction, format="markdown"
            )
            ET.SubElement(answer, "text").text = answer_text
            feedback = ET.SubElement(answer, "feedback", format="markdown")
            ET.SubElement(feedback, "text")

    def _render_tf(self, parent: ET.Element, q: TrueFalse) -> None:
        q_elem = ET.SubElement(parent, "question", type="truefalse")

        name = ET.SubElement(q_elem, "name")
        ET.SubElement(name, "text").text = q.name

        qtext = ET.SubElement(q_elem, "questiontext", format="markdown")
        ET.SubElement(qtext, "text").text = q.text

        gen_feedback = ET.SubElement(q_elem, "generalfeedback", format="markdown")
        ET.SubElement(gen_feedback, "text")

        ET.SubElement(q_elem, "defaultgrade").text = "1.0000000"
        ET.SubElement(q_elem, "penalty").text = "1.0000000"
        ET.SubElement(q_elem, "hidden").text = "0"
        ET.SubElement(q_elem, "idnumber")

        if q.correct_answer:
            correct, wrong = "true", "false"
        else:
            correct, wrong = "false", "true"

        # Wrong answer (fraction 0)
        ans_wrong = ET.SubElement(
            q_elem, "answer", fraction="0", format="moodle_auto_format"
        )
        ET.SubElement(ans_wrong, "text").text = wrong
        feed_wrong = ET.SubElement(ans_wrong, "feedback", format="markdown")
        ET.SubElement(feed_wrong, "text")

        # Correct answer (fraction 100)
        ans_correct = ET.SubElement(q_elem, "answer", fraction="100", format="markdown")
        ET.SubElement(ans_correct, "text").text = correct
        feed_correct = ET.SubElement(ans_correct, "feedback", format="markdown")
        ET.SubElement(feed_correct, "text")

    def _render_cloze(self, parent: ET.Element, q: Cloze) -> None:
        q_elem = ET.SubElement(parent, "question", type="cloze")

        name = ET.SubElement(q_elem, "name")
        ET.SubElement(name, "text").text = q.name

        qtext = ET.SubElement(q_elem, "questiontext", format="markdown")
        ET.SubElement(qtext, "text").text = q.text

        gen_feedback = ET.SubElement(q_elem, "generalfeedback", format="markdown")
        ET.SubElement(gen_feedback, "text")

        ET.SubElement(q_elem, "penalty").text = "0.3333333"
        ET.SubElement(q_elem, "hidden").text = "0"
        ET.SubElement(q_elem, "idnumber")
