"""
Tests fuer die Klausur-Korrektur API

Tests fuer:
- Klausuren erstellen, abrufen, aktualisieren, loeschen
- Text-Quellen hinzufuegen und verwalten
- Schuelerarbeiten hochladen
- Bewertung und Gutachten
- 15-Punkte-Notensystem
"""

import pytest
from unittest.mock import MagicMock, patch
from datetime import datetime

# Import des zu testenden Moduls
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from klausur_korrektur_api import (
    router,
    AbiturKlausur,
    KlausurModus,
    KlausurStatus,
    TextSource,
    TextSourceType,
    TextSourceStatus,
    StudentKlausur,
    StudentKlausurStatus,
    Erwartungshorizont,
    Aufgabe,
    CriterionScore,
    Gutachten,
    ExaminerResult,
    DEFAULT_CRITERIA,
    GRADE_THRESHOLDS,
    calculate_15_point_grade,
    klausuren_db,
)


class TestGradeCalculation:
    """Tests fuer die Notenberechnung im 15-Punkte-System."""

    def test_calculate_15_point_grade_perfect(self):
        """100% sollte 15 Punkte ergeben."""
        assert calculate_15_point_grade(100.0) == 15

    def test_calculate_15_point_grade_95(self):
        """95% sollte 15 Punkte ergeben (1+)."""
        assert calculate_15_point_grade(95.0) == 15

    def test_calculate_15_point_grade_90(self):
        """90% sollte 14 Punkte ergeben (1)."""
        assert calculate_15_point_grade(90.0) == 14

    def test_calculate_15_point_grade_85(self):
        """85% sollte 13 Punkte ergeben (1-)."""
        assert calculate_15_point_grade(85.0) == 13

    def test_calculate_15_point_grade_50(self):
        """50% sollte 6 Punkte ergeben (4+)."""
        assert calculate_15_point_grade(50.0) == 6

    def test_calculate_15_point_grade_45(self):
        """45% sollte 5 Punkte ergeben (4)."""
        assert calculate_15_point_grade(45.0) == 5

    def test_calculate_15_point_grade_below_threshold(self):
        """19% sollte 0 Punkte ergeben (6)."""
        assert calculate_15_point_grade(19.0) == 0

    def test_calculate_15_point_grade_zero(self):
        """0% sollte 0 Punkte ergeben."""
        assert calculate_15_point_grade(0.0) == 0

    def test_calculate_15_point_grade_boundary_values(self):
        """Test aller Grenzwerte."""
        expected_results = [
            (95, 15),
            (94.9, 14),
            (90, 14),
            (89.9, 13),
            (85, 13),
            (84.9, 12),
            (80, 12),
            (79.9, 11),
            (75, 11),
            (74.9, 10),
            (70, 10),
            (69.9, 9),
            (65, 9),
            (64.9, 8),
            (60, 8),
            (59.9, 7),
            (55, 7),
            (54.9, 6),
            (50, 6),
            (49.9, 5),
            (45, 5),
            (44.9, 4),
            (40, 4),
            (39.9, 3),
            (33, 3),
            (32.9, 2),
            (27, 2),
            (26.9, 1),
            (20, 1),
            (19.9, 0),
        ]
        for percentage, expected_points in expected_results:
            result = calculate_15_point_grade(percentage)
            assert result == expected_points, f"Expected {expected_points} for {percentage}%, got {result}"


class TestGradeThresholds:
    """Tests fuer die Notenschwellen."""

    def test_all_thresholds_present(self):
        """Alle 16 Notenpunkte (0-15) sollten definiert sein."""
        assert len(GRADE_THRESHOLDS) == 16
        for i in range(16):
            assert i in GRADE_THRESHOLDS

    def test_thresholds_descending(self):
        """Schwellen sollten von 15 nach 0 absteigend sein."""
        prev_threshold = 100
        for points in range(15, -1, -1):
            threshold = GRADE_THRESHOLDS[points]
            assert threshold < prev_threshold or (points == 15 and threshold <= prev_threshold)
            prev_threshold = threshold


class TestDefaultCriteria:
    """Tests fuer die Standard-Bewertungskriterien."""

    def test_criteria_weights_sum_to_one(self):
        """Gewichte aller Kriterien sollten 1.0 ergeben."""
        total_weight = sum(c["weight"] for c in DEFAULT_CRITERIA.values())
        assert abs(total_weight - 1.0) < 0.001

    def test_required_criteria_present(self):
        """Alle erforderlichen Kriterien sollten vorhanden sein."""
        required = ["rechtschreibung", "grammatik", "inhalt", "struktur", "stil"]
        for criterion in required:
            assert criterion in DEFAULT_CRITERIA

    def test_inhalt_has_highest_weight(self):
        """Inhalt sollte das hoechste Gewicht haben."""
        inhalt_weight = DEFAULT_CRITERIA["inhalt"]["weight"]
        for name, criterion in DEFAULT_CRITERIA.items():
            if name != "inhalt":
                assert criterion["weight"] <= inhalt_weight


class TestKlausurModels:
    """Tests fuer die Datenmodelle."""

    def test_create_abitur_klausur(self):
        """Eine neue Klausur sollte erstellt werden koennen."""
        now = datetime.now()
        klausur = AbiturKlausur(
            id="test-123",
            title="Deutsch LK Q4",
            subject="deutsch",
            modus=KlausurModus.LANDES_ABITUR,
            year=2025,
            semester="Q4",
            kurs="LK",
            class_id=None,
            status=KlausurStatus.DRAFT,
            text_sources=[],
            erwartungshorizont=None,
            students=[],
            created_at=now,
            updated_at=now
        )
        assert klausur.id == "test-123"
        assert klausur.modus == KlausurModus.LANDES_ABITUR

    def test_create_text_source(self):
        """Eine Textquelle sollte erstellt werden koennen."""
        source = TextSource(
            id="src-1",
            source_type=TextSourceType.NIBIS,
            title="Kafka - Die Verwandlung",
            author="Franz Kafka",
            content="Als Gregor Samsa eines Morgens...",
            nibis_id=None,
            license_status=TextSourceStatus.VERIFIED,
            license_info={"license": "PD"},
            created_at=datetime.now()
        )
        assert source.license_status == TextSourceStatus.VERIFIED

    def test_student_klausur_status_workflow(self):
        """Der Status-Workflow einer Schuelerarbeit sollte korrekt sein."""
        statuses = list(StudentKlausurStatus)
        expected_order = [
            StudentKlausurStatus.UPLOADED,
            StudentKlausurStatus.OCR_PROCESSING,
            StudentKlausurStatus.OCR_COMPLETE,
            StudentKlausurStatus.ANALYZING,
            StudentKlausurStatus.FIRST_EXAMINER,
            StudentKlausurStatus.SECOND_EXAMINER,
            StudentKlausurStatus.COMPLETED,
            StudentKlausurStatus.ERROR,  # Error state can occur at any point
        ]
        assert statuses == expected_order


class TestCriterionScore:
    """Tests fuer die Bewertungskriterien-Punkte."""

    def test_create_criterion_score(self):
        """Ein Kriterium-Score sollte erstellt werden koennen."""
        score = CriterionScore(
            score=85,
            weight=0.4,
            annotations=["Gute Argumentation"],
            comment="Insgesamt gut",
            ai_suggestions=["Mehr Beispiele hinzufuegen"]
        )
        assert score.score == 85
        assert score.weight == 0.4

    def test_weighted_score_calculation(self):
        """Der gewichtete Score sollte korrekt berechnet werden."""
        score = CriterionScore(
            score=80,
            weight=0.4,
            annotations=[],
            comment="",
            ai_suggestions=[]
        )
        weighted = score.score * score.weight
        assert weighted == 32.0


class TestExpectationHorizon:
    """Tests fuer den Erwartungshorizont."""

    def test_create_aufgabe(self):
        """Eine Aufgabe sollte erstellt werden koennen."""
        aufgabe = Aufgabe(
            id="aufg-1",
            nummer="1a",
            text="Analysieren Sie das Gedicht.",
            operator="analysieren",
            anforderungsbereich=2,
            erwartete_leistungen=["Epoche erkennen", "Stilmittel benennen"],
            punkte=20
        )
        assert aufgabe.anforderungsbereich == 2
        assert aufgabe.punkte == 20

    def test_create_erwartungshorizont(self):
        """Ein Erwartungshorizont sollte erstellt werden koennen."""
        aufgaben = [
            Aufgabe(id="a1", nummer="1", text="Aufgabe 1", operator="analysieren", anforderungsbereich=2,
                    erwartete_leistungen=["Test"], punkte=30),
            Aufgabe(id="a2", nummer="2", text="Aufgabe 2", operator="erlaeutern", anforderungsbereich=2,
                    erwartete_leistungen=["Test2"], punkte=30),
            Aufgabe(id="a3", nummer="3", text="Aufgabe 3", operator="beurteilen", anforderungsbereich=3,
                    erwartete_leistungen=["Test3"], punkte=40),
        ]
        ewh = Erwartungshorizont(
            id="ewh-1",
            aufgaben=aufgaben,
            max_points=100,
            hinweise="Allgemeine Hinweise",
            generated=False,
            created_at=datetime.now()
        )
        assert ewh.max_points == 100
        assert len(ewh.aufgaben) == 3
        total_points = sum(a.punkte for a in ewh.aufgaben)
        assert total_points == 100


class TestKlausurDB:
    """Tests fuer die In-Memory Datenbank."""

    def setup_method(self):
        """Setup vor jedem Test - leere die DB."""
        klausuren_db.clear()

    def test_empty_db(self):
        """Eine leere DB sollte leer sein."""
        assert len(klausuren_db) == 0

    def test_add_klausur_to_db(self):
        """Eine Klausur sollte zur DB hinzugefuegt werden koennen."""
        now = datetime.now()
        klausur = AbiturKlausur(
            id="test-1",
            title="Test Klausur",
            subject="deutsch",
            modus=KlausurModus.VORABITUR,
            year=2025,
            semester="Q3",
            kurs="GK",
            class_id=None,
            status=KlausurStatus.DRAFT,
            text_sources=[],
            erwartungshorizont=None,
            students=[],
            created_at=now,
            updated_at=now
        )
        klausuren_db["test-1"] = klausur
        assert "test-1" in klausuren_db
        assert klausuren_db["test-1"].title == "Test Klausur"


class TestKlausurModus:
    """Tests fuer die Klausur-Modi."""

    def test_landes_abitur_mode(self):
        """Landes-Abitur Modus sollte existieren."""
        assert KlausurModus.LANDES_ABITUR.value == "landes_abitur"

    def test_vorabitur_mode(self):
        """Vorabitur Modus sollte existieren."""
        assert KlausurModus.VORABITUR.value == "vorabitur"


class TestTextSourceStatus:
    """Tests fuer den TextSource-Status."""

    def test_pending_status(self):
        """Pending Status sollte existieren."""
        assert TextSourceStatus.PENDING.value == "pending"

    def test_verified_status(self):
        """Verified Status sollte existieren."""
        assert TextSourceStatus.VERIFIED.value == "verified"

    def test_rejected_status(self):
        """Rejected Status sollte existieren."""
        assert TextSourceStatus.REJECTED.value == "rejected"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
