"""
Quiz Generator - Erstellt verschiedene Quiz-Typen aus Quelltexten.

Generiert:
- True/False Fragen
- Zuordnungsaufgaben (Matching)
- Sortieraufgaben
- Offene Fragen mit Musterlösungen
"""

import logging
import json
import re
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass
from enum import Enum

logger = logging.getLogger(__name__)


class QuizType(str, Enum):
    """Typen von Quiz-Aufgaben."""
    TRUE_FALSE = "true_false"
    MATCHING = "matching"
    SORTING = "sorting"
    OPEN_ENDED = "open_ended"


@dataclass
class TrueFalseQuestion:
    """Eine Wahr/Falsch-Frage."""
    statement: str
    is_true: bool
    explanation: str
    source_reference: Optional[str] = None


@dataclass
class MatchingPair:
    """Ein Zuordnungspaar."""
    left: str
    right: str
    hint: Optional[str] = None


@dataclass
class SortingItem:
    """Ein Element zum Sortieren."""
    text: str
    correct_position: int
    category: Optional[str] = None


@dataclass
class OpenQuestion:
    """Eine offene Frage."""
    question: str
    model_answer: str
    keywords: List[str]
    points: int = 1


@dataclass
class Quiz:
    """Ein komplettes Quiz."""
    quiz_type: QuizType
    title: str
    questions: List[Any]  # Je nach Typ unterschiedlich
    topic: Optional[str] = None
    difficulty: str = "medium"


class QuizGenerator:
    """
    Generiert verschiedene Quiz-Typen aus Quelltexten.
    """

    def __init__(self, llm_client=None):
        """
        Initialisiert den Generator.

        Args:
            llm_client: Optional - LLM-Client für intelligente Generierung
        """
        self.llm_client = llm_client
        logger.info("QuizGenerator initialized")

    def generate(
        self,
        source_text: str,
        quiz_type: QuizType,
        num_questions: int = 5,
        title: Optional[str] = None,
        topic: Optional[str] = None,
        difficulty: str = "medium"
    ) -> Quiz:
        """
        Generiert ein Quiz aus einem Quelltext.

        Args:
            source_text: Der Ausgangstext
            quiz_type: Art des Quiz
            num_questions: Anzahl der Fragen/Aufgaben
            title: Optionaler Titel
            topic: Optionales Thema
            difficulty: Schwierigkeitsgrad

        Returns:
            Quiz-Objekt
        """
        logger.info(f"Generating {quiz_type} quiz with {num_questions} questions")

        if not source_text or len(source_text.strip()) < 50:
            logger.warning("Source text too short")
            return self._empty_quiz(quiz_type, title or "Quiz")

        generators = {
            QuizType.TRUE_FALSE: self._generate_true_false,
            QuizType.MATCHING: self._generate_matching,
            QuizType.SORTING: self._generate_sorting,
            QuizType.OPEN_ENDED: self._generate_open_ended,
        }

        generator = generators.get(quiz_type)
        if not generator:
            raise ValueError(f"Unbekannter Quiz-Typ: {quiz_type}")

        questions = generator(source_text, num_questions, difficulty)

        return Quiz(
            quiz_type=quiz_type,
            title=title or f"{quiz_type.value.replace('_', ' ').title()} Quiz",
            questions=questions,
            topic=topic,
            difficulty=difficulty
        )

    def _generate_true_false(
        self,
        source_text: str,
        num_questions: int,
        difficulty: str
    ) -> List[TrueFalseQuestion]:
        """Generiert Wahr/Falsch-Fragen."""
        if self.llm_client:
            return self._generate_true_false_llm(source_text, num_questions, difficulty)

        # Automatische Generierung
        sentences = self._extract_factual_sentences(source_text)
        questions = []

        for i, sentence in enumerate(sentences[:num_questions]):
            # Abwechselnd wahre und falsche Aussagen
            if i % 2 == 0:
                # Wahre Aussage
                questions.append(TrueFalseQuestion(
                    statement=sentence,
                    is_true=True,
                    explanation="Diese Aussage entspricht dem Text.",
                    source_reference=sentence[:50]
                ))
            else:
                # Falsche Aussage (Negation)
                false_statement = self._negate_sentence(sentence)
                questions.append(TrueFalseQuestion(
                    statement=false_statement,
                    is_true=False,
                    explanation=f"Richtig wäre: {sentence}",
                    source_reference=sentence[:50]
                ))

        return questions

    def _generate_true_false_llm(
        self,
        source_text: str,
        num_questions: int,
        difficulty: str
    ) -> List[TrueFalseQuestion]:
        """Generiert Wahr/Falsch-Fragen mit LLM."""
        prompt = f"""
Erstelle {num_questions} Wahr/Falsch-Aussagen auf Deutsch basierend auf folgendem Text.
Schwierigkeit: {difficulty}
Erstelle etwa gleich viele wahre und falsche Aussagen.

Text:
{source_text}

Antworte im JSON-Format:
{{
  "questions": [
    {{
      "statement": "Die Aussage...",
      "is_true": true,
      "explanation": "Erklärung warum wahr/falsch"
    }}
  ]
}}
"""
        try:
            response = self.llm_client.generate(prompt)
            data = json.loads(response)
            return [
                TrueFalseQuestion(
                    statement=q["statement"],
                    is_true=q["is_true"],
                    explanation=q["explanation"]
                )
                for q in data.get("questions", [])
            ]
        except Exception as e:
            logger.error(f"LLM error: {e}")
            return self._generate_true_false(source_text, num_questions, difficulty)

    def _generate_matching(
        self,
        source_text: str,
        num_pairs: int,
        difficulty: str
    ) -> List[MatchingPair]:
        """Generiert Zuordnungsaufgaben."""
        if self.llm_client:
            return self._generate_matching_llm(source_text, num_pairs, difficulty)

        # Automatische Generierung: Begriff -> Definition
        pairs = []
        definitions = self._extract_definitions(source_text)

        for term, definition in definitions[:num_pairs]:
            pairs.append(MatchingPair(
                left=term,
                right=definition,
                hint=f"Beginnt mit '{definition[0]}'"
            ))

        return pairs

    def _generate_matching_llm(
        self,
        source_text: str,
        num_pairs: int,
        difficulty: str
    ) -> List[MatchingPair]:
        """Generiert Zuordnungen mit LLM."""
        prompt = f"""
Erstelle {num_pairs} Zuordnungspaare auf Deutsch basierend auf folgendem Text.
Jedes Paar besteht aus einem Begriff und seiner Definition/Erklärung.
Schwierigkeit: {difficulty}

Text:
{source_text}

Antworte im JSON-Format:
{{
  "pairs": [
    {{
      "term": "Begriff",
      "definition": "Definition des Begriffs",
      "hint": "Optionaler Hinweis"
    }}
  ]
}}
"""
        try:
            response = self.llm_client.generate(prompt)
            data = json.loads(response)
            return [
                MatchingPair(
                    left=p["term"],
                    right=p["definition"],
                    hint=p.get("hint")
                )
                for p in data.get("pairs", [])
            ]
        except Exception as e:
            logger.error(f"LLM error: {e}")
            return self._generate_matching(source_text, num_pairs, difficulty)

    def _generate_sorting(
        self,
        source_text: str,
        num_items: int,
        difficulty: str
    ) -> List[SortingItem]:
        """Generiert Sortieraufgaben."""
        if self.llm_client:
            return self._generate_sorting_llm(source_text, num_items, difficulty)

        # Automatische Generierung: Chronologische Reihenfolge
        items = []
        steps = self._extract_sequence(source_text)

        for i, step in enumerate(steps[:num_items]):
            items.append(SortingItem(
                text=step,
                correct_position=i + 1
            ))

        return items

    def _generate_sorting_llm(
        self,
        source_text: str,
        num_items: int,
        difficulty: str
    ) -> List[SortingItem]:
        """Generiert Sortierung mit LLM."""
        prompt = f"""
Erstelle eine Sortieraufgabe auf Deutsch basierend auf folgendem Text.
Finde {num_items} Elemente, die in eine logische Reihenfolge gebracht werden müssen.
(z.B. chronologisch, nach Wichtigkeit, nach Größe, etc.)
Schwierigkeit: {difficulty}

Text:
{source_text}

Antworte im JSON-Format:
{{
  "category": "chronologisch/nach Größe/etc.",
  "items": [
    {{"text": "Erstes Element", "position": 1}},
    {{"text": "Zweites Element", "position": 2}}
  ]
}}
"""
        try:
            response = self.llm_client.generate(prompt)
            data = json.loads(response)
            category = data.get("category")
            return [
                SortingItem(
                    text=item["text"],
                    correct_position=item["position"],
                    category=category
                )
                for item in data.get("items", [])
            ]
        except Exception as e:
            logger.error(f"LLM error: {e}")
            return self._generate_sorting(source_text, num_items, difficulty)

    def _generate_open_ended(
        self,
        source_text: str,
        num_questions: int,
        difficulty: str
    ) -> List[OpenQuestion]:
        """Generiert offene Fragen."""
        if self.llm_client:
            return self._generate_open_ended_llm(source_text, num_questions, difficulty)

        # Automatische Generierung
        questions = []
        sentences = self._extract_factual_sentences(source_text)

        question_starters = [
            "Was bedeutet",
            "Erkläre",
            "Warum",
            "Wie funktioniert",
            "Nenne die Hauptmerkmale von"
        ]

        for i, sentence in enumerate(sentences[:num_questions]):
            # Extrahiere Schlüsselwort
            keywords = self._extract_keywords(sentence)
            if keywords:
                keyword = keywords[0]
                starter = question_starters[i % len(question_starters)]
                question = f"{starter} '{keyword}'?"

                questions.append(OpenQuestion(
                    question=question,
                    model_answer=sentence,
                    keywords=keywords,
                    points=1
                ))

        return questions

    def _generate_open_ended_llm(
        self,
        source_text: str,
        num_questions: int,
        difficulty: str
    ) -> List[OpenQuestion]:
        """Generiert offene Fragen mit LLM."""
        prompt = f"""
Erstelle {num_questions} offene Fragen auf Deutsch basierend auf folgendem Text.
Jede Frage sollte eine ausführliche Antwort erfordern.
Schwierigkeit: {difficulty}

Text:
{source_text}

Antworte im JSON-Format:
{{
  "questions": [
    {{
      "question": "Die Frage...",
      "model_answer": "Eine vollständige Musterantwort",
      "keywords": ["Schlüsselwort1", "Schlüsselwort2"],
      "points": 2
    }}
  ]
}}
"""
        try:
            response = self.llm_client.generate(prompt)
            data = json.loads(response)
            return [
                OpenQuestion(
                    question=q["question"],
                    model_answer=q["model_answer"],
                    keywords=q.get("keywords", []),
                    points=q.get("points", 1)
                )
                for q in data.get("questions", [])
            ]
        except Exception as e:
            logger.error(f"LLM error: {e}")
            return self._generate_open_ended(source_text, num_questions, difficulty)

    # Hilfsmethoden

    def _extract_factual_sentences(self, text: str) -> List[str]:
        """Extrahiert Fakten-Sätze aus dem Text."""
        sentences = re.split(r'[.!?]+', text)
        factual = []

        for sentence in sentences:
            sentence = sentence.strip()
            # Filtere zu kurze oder fragende Sätze
            if len(sentence) > 20 and '?' not in sentence:
                factual.append(sentence)

        return factual

    def _negate_sentence(self, sentence: str) -> str:
        """Negiert eine Aussage einfach."""
        # Einfache Negation durch Einfügen von "nicht"
        words = sentence.split()
        if len(words) > 2:
            # Nach erstem Verb "nicht" einfügen
            for i, word in enumerate(words):
                if word.endswith(('t', 'en', 'st')) and i > 0:
                    words.insert(i + 1, 'nicht')
                    break
        return ' '.join(words)

    def _extract_definitions(self, text: str) -> List[Tuple[str, str]]:
        """Extrahiert Begriff-Definition-Paare."""
        definitions = []

        # Suche nach Mustern wie "X ist Y" oder "X bezeichnet Y"
        patterns = [
            r'(\w+)\s+ist\s+(.+?)[.]',
            r'(\w+)\s+bezeichnet\s+(.+?)[.]',
            r'(\w+)\s+bedeutet\s+(.+?)[.]',
            r'(\w+):\s+(.+?)[.]',
        ]

        for pattern in patterns:
            matches = re.findall(pattern, text)
            for term, definition in matches:
                if len(definition) > 10:
                    definitions.append((term, definition.strip()))

        return definitions

    def _extract_sequence(self, text: str) -> List[str]:
        """Extrahiert eine Sequenz von Schritten."""
        steps = []

        # Suche nach nummerierten Schritten
        numbered = re.findall(r'\d+[.)]\s*([^.]+)', text)
        steps.extend(numbered)

        # Suche nach Signalwörtern
        signal_words = ['zuerst', 'dann', 'danach', 'anschließend', 'schließlich']
        for word in signal_words:
            pattern = rf'{word}\s+([^.]+)'
            matches = re.findall(pattern, text, re.IGNORECASE)
            steps.extend(matches)

        return steps

    def _extract_keywords(self, text: str) -> List[str]:
        """Extrahiert Schlüsselwörter."""
        # Längere Wörter mit Großbuchstaben (meist Substantive)
        words = re.findall(r'\b[A-ZÄÖÜ][a-zäöüß]+\b', text)
        return list(set(words))[:5]

    def _empty_quiz(self, quiz_type: QuizType, title: str) -> Quiz:
        """Erstellt leeres Quiz bei Fehler."""
        return Quiz(
            quiz_type=quiz_type,
            title=title,
            questions=[],
            difficulty="medium"
        )

    def to_dict(self, quiz: Quiz) -> Dict[str, Any]:
        """Konvertiert Quiz zu Dictionary-Format."""
        questions_data = []

        for q in quiz.questions:
            if isinstance(q, TrueFalseQuestion):
                questions_data.append({
                    "type": "true_false",
                    "statement": q.statement,
                    "is_true": q.is_true,
                    "explanation": q.explanation
                })
            elif isinstance(q, MatchingPair):
                questions_data.append({
                    "type": "matching",
                    "left": q.left,
                    "right": q.right,
                    "hint": q.hint
                })
            elif isinstance(q, SortingItem):
                questions_data.append({
                    "type": "sorting",
                    "text": q.text,
                    "correct_position": q.correct_position,
                    "category": q.category
                })
            elif isinstance(q, OpenQuestion):
                questions_data.append({
                    "type": "open_ended",
                    "question": q.question,
                    "model_answer": q.model_answer,
                    "keywords": q.keywords,
                    "points": q.points
                })

        return {
            "quiz_type": quiz.quiz_type.value,
            "title": quiz.title,
            "topic": quiz.topic,
            "difficulty": quiz.difficulty,
            "questions": questions_data
        }

    def to_h5p_format(self, quiz: Quiz) -> Dict[str, Any]:
        """Konvertiert Quiz ins H5P-Format."""
        if quiz.quiz_type == QuizType.TRUE_FALSE:
            return self._true_false_to_h5p(quiz)
        elif quiz.quiz_type == QuizType.MATCHING:
            return self._matching_to_h5p(quiz)
        # Weitere Typen...
        return {}

    def _true_false_to_h5p(self, quiz: Quiz) -> Dict[str, Any]:
        """Konvertiert True/False zu H5P."""
        statements = []
        for q in quiz.questions:
            statements.append({
                "text": q.statement,
                "correct": q.is_true,
                "feedback": q.explanation
            })

        return {
            "library": "H5P.TrueFalse",
            "params": {
                "statements": statements,
                "behaviour": {
                    "enableRetry": True,
                    "enableSolutionsButton": True
                }
            }
        }

    def _matching_to_h5p(self, quiz: Quiz) -> Dict[str, Any]:
        """Konvertiert Matching zu H5P."""
        pairs = []
        for q in quiz.questions:
            pairs.append({
                "question": q.left,
                "answer": q.right
            })

        return {
            "library": "H5P.DragText",
            "params": {
                "pairs": pairs,
                "behaviour": {
                    "enableRetry": True,
                    "enableSolutionsButton": True
                }
            }
        }
