"""
Tests für den Communication Service.

Testet die KI-gestützte Lehrer-Eltern-Kommunikation mit GFK-Prinzipien.
"""

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

from llm_gateway.services.communication_service import (
    CommunicationService,
    CommunicationType,
    CommunicationTone,
    LegalReference,
    GFKPrinciple,
    get_communication_service,
    fetch_legal_references_from_db,
    parse_db_references_to_legal_refs,
    FALLBACK_LEGAL_REFERENCES,
    GFK_PRINCIPLES,
)


class TestCommunicationType:
    """Tests für CommunicationType Enum."""

    def test_all_communication_types_exist(self):
        """Test alle erwarteten Kommunikationstypen existieren."""
        expected_types = [
            "general_info",
            "behavior",
            "academic",
            "attendance",
            "meeting_invite",
            "positive_feedback",
            "concern",
            "conflict",
            "special_needs",
        ]
        actual_types = [ct.value for ct in CommunicationType]
        assert set(expected_types) == set(actual_types)

    def test_communication_type_is_string_enum(self):
        """Test CommunicationType ist String Enum."""
        assert CommunicationType.BEHAVIOR == "behavior"
        assert CommunicationType.ACADEMIC.value == "academic"


class TestCommunicationTone:
    """Tests für CommunicationTone Enum."""

    def test_all_tones_exist(self):
        """Test alle Tonalitäten existieren."""
        expected_tones = ["formal", "professional", "warm", "concerned", "appreciative"]
        actual_tones = [t.value for t in CommunicationTone]
        assert set(expected_tones) == set(actual_tones)


class TestLegalReference:
    """Tests für LegalReference Dataclass."""

    def test_legal_reference_creation(self):
        """Test LegalReference erstellen."""
        ref = LegalReference(
            law="SchulG NRW",
            paragraph="§ 42",
            title="Pflichten der Eltern",
            summary="Eltern unterstützen die Schule.",
            relevance="Kooperationsaufforderungen",
        )
        assert ref.law == "SchulG NRW"
        assert ref.paragraph == "§ 42"
        assert "Eltern" in ref.title


class TestGFKPrinciple:
    """Tests für GFKPrinciple Dataclass."""

    def test_gfk_principle_creation(self):
        """Test GFKPrinciple erstellen."""
        principle = GFKPrinciple(
            principle="Beobachtung",
            description="Konkrete Handlungen beschreiben",
            example="Ich habe bemerkt...",
        )
        assert principle.principle == "Beobachtung"
        assert "beschreiben" in principle.description


class TestFallbackLegalReferences:
    """Tests für die Fallback-Referenzen."""

    def test_default_references_exist(self):
        """Test DEFAULT Referenzen existieren."""
        assert "DEFAULT" in FALLBACK_LEGAL_REFERENCES
        assert "elternpflichten" in FALLBACK_LEGAL_REFERENCES["DEFAULT"]
        assert "schulpflicht" in FALLBACK_LEGAL_REFERENCES["DEFAULT"]

    def test_fallback_references_are_legal_reference(self):
        """Test Fallback Referenzen sind LegalReference Objekte."""
        ref = FALLBACK_LEGAL_REFERENCES["DEFAULT"]["elternpflichten"]
        assert isinstance(ref, LegalReference)
        assert ref.law == "Landesschulgesetz"


class TestGFKPrinciples:
    """Tests für GFK-Prinzipien."""

    def test_four_gfk_principles_exist(self):
        """Test alle 4 GFK-Prinzipien existieren."""
        assert len(GFK_PRINCIPLES) == 4
        principles = [p.principle for p in GFK_PRINCIPLES]
        assert "Beobachtung" in principles
        assert "Gefühle" in principles
        assert "Bedürfnisse" in principles
        assert "Bitten" in principles


class TestCommunicationService:
    """Tests für CommunicationService Klasse."""

    def test_service_initialization(self):
        """Test Service wird korrekt initialisiert."""
        service = CommunicationService()
        assert service.fallback_references is not None
        assert service.gfk_principles is not None
        assert service.templates is not None
        assert service._cached_references == {}

    def test_get_legal_references_sync(self):
        """Test synchrone get_legal_references Methode (Fallback)."""
        service = CommunicationService()
        refs = service.get_legal_references("NRW", "elternpflichten")

        assert len(refs) > 0
        assert isinstance(refs[0], LegalReference)

    def test_get_fallback_references(self):
        """Test _get_fallback_references Methode."""
        service = CommunicationService()
        refs = service._get_fallback_references("DEFAULT", "elternpflichten")

        assert len(refs) == 1
        assert refs[0].law == "Landesschulgesetz"

    def test_get_gfk_guidance(self):
        """Test get_gfk_guidance gibt GFK-Prinzipien zurück."""
        service = CommunicationService()
        guidance = service.get_gfk_guidance(CommunicationType.BEHAVIOR)

        assert len(guidance) == 4
        assert all(isinstance(g, GFKPrinciple) for g in guidance)

    def test_get_template(self):
        """Test get_template gibt Vorlage zurück."""
        service = CommunicationService()
        template = service.get_template(CommunicationType.MEETING_INVITE)

        assert "subject" in template
        assert "opening" in template
        assert "closing" in template
        assert "Einladung" in template["subject"]

    def test_get_template_fallback(self):
        """Test get_template Fallback zu GENERAL_INFO."""
        service = CommunicationService()
        # Unbekannter Typ sollte auf GENERAL_INFO fallen
        template = service.get_template(CommunicationType.GENERAL_INFO)

        assert "Information" in template["subject"]


class TestCommunicationServiceAsync:
    """Async Tests für CommunicationService."""

    @pytest.mark.asyncio
    async def test_get_legal_references_async_with_db(self):
        """Test async get_legal_references_async mit DB-Daten."""
        service = CommunicationService()

        # Mock die fetch_legal_references_from_db Funktion
        mock_docs = [
            {
                "law_name": "SchulG NRW",
                "title": "Schulgesetz NRW",
                "paragraphs": [
                    {"nr": "§ 42", "title": "Pflichten der Eltern"},
                    {"nr": "§ 41", "title": "Schulpflicht"},
                ],
            }
        ]

        with patch(
            "llm_gateway.services.communication_service.fetch_legal_references_from_db",
            new_callable=AsyncMock,
        ) as mock_fetch:
            mock_fetch.return_value = mock_docs

            refs = await service.get_legal_references_async("NRW", "elternpflichten")

            assert len(refs) > 0
            mock_fetch.assert_called_once_with("NRW")

    @pytest.mark.asyncio
    async def test_get_legal_references_async_fallback(self):
        """Test async get_legal_references_async Fallback wenn DB leer."""
        service = CommunicationService()

        with patch(
            "llm_gateway.services.communication_service.fetch_legal_references_from_db",
            new_callable=AsyncMock,
        ) as mock_fetch:
            mock_fetch.return_value = []  # Leere DB

            refs = await service.get_legal_references_async("NRW", "elternpflichten")

            # Sollte Fallback nutzen
            assert len(refs) > 0
            assert refs[0].law == "Landesschulgesetz"

    @pytest.mark.asyncio
    async def test_get_legal_references_async_caching(self):
        """Test dass Ergebnisse gecached werden."""
        service = CommunicationService()

        mock_docs = [
            {
                "law_name": "SchulG NRW",
                "paragraphs": [{"nr": "§ 42", "title": "Pflichten der Eltern"}],
            }
        ]

        with patch(
            "llm_gateway.services.communication_service.fetch_legal_references_from_db",
            new_callable=AsyncMock,
        ) as mock_fetch:
            mock_fetch.return_value = mock_docs

            # Erster Aufruf
            await service.get_legal_references_async("NRW", "elternpflichten")

            # Zweiter Aufruf sollte Cache nutzen
            await service.get_legal_references_async("NRW", "elternpflichten")

            # fetch sollte nur einmal aufgerufen werden
            assert mock_fetch.call_count == 1


class TestFetchLegalReferencesFromDB:
    """Tests für fetch_legal_references_from_db Funktion."""

    @pytest.mark.asyncio
    async def test_fetch_success(self):
        """Test erfolgreicher API-Aufruf."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "documents": [
                {"law_name": "SchulG NRW", "paragraphs": []},
            ]
        }

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            docs = await fetch_legal_references_from_db("NRW")

            assert len(docs) == 1
            assert docs[0]["law_name"] == "SchulG NRW"

    @pytest.mark.asyncio
    async def test_fetch_api_error(self):
        """Test API-Fehler gibt leere Liste zurück."""
        mock_response = MagicMock()
        mock_response.status_code = 500

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            docs = await fetch_legal_references_from_db("NRW")

            assert docs == []

    @pytest.mark.asyncio
    async def test_fetch_network_error(self):
        """Test Netzwerkfehler gibt leere Liste zurück."""
        import httpx

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.ConnectError("Connection failed")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            docs = await fetch_legal_references_from_db("NRW")

            assert docs == []


class TestParseDbReferencesToLegalRefs:
    """Tests für parse_db_references_to_legal_refs Funktion."""

    def test_parse_with_matching_paragraphs(self):
        """Test Parsing mit passenden Paragraphen."""
        db_docs = [
            {
                "law_name": "SchulG NRW",
                "title": "Schulgesetz",
                "paragraphs": [
                    {"nr": "§ 42", "title": "Pflichten der Eltern"},
                    {"nr": "§ 1", "title": "Bildungsauftrag"},
                ],
            }
        ]

        refs = parse_db_references_to_legal_refs(db_docs, "elternpflichten")

        assert len(refs) > 0
        # § 42 sollte für elternpflichten relevant sein
        assert any("42" in r.paragraph for r in refs)

    def test_parse_without_paragraphs(self):
        """Test Parsing ohne Paragraphen."""
        db_docs = [
            {
                "law_name": "SchulG NRW",
                "title": "Schulgesetz",
                "paragraphs": [],
            }
        ]

        refs = parse_db_references_to_legal_refs(db_docs, "elternpflichten")

        # Sollte trotzdem Referenz erstellen
        assert len(refs) == 1
        assert refs[0].law == "SchulG NRW"

    def test_parse_empty_docs(self):
        """Test Parsing mit leerer Dokumentenliste."""
        refs = parse_db_references_to_legal_refs([], "elternpflichten")

        assert refs == []


class TestBuildSystemPrompt:
    """Tests für build_system_prompt Methode."""

    def test_build_system_prompt_contains_gfk(self):
        """Test System-Prompt enthält GFK-Prinzipien."""
        service = CommunicationService()
        prompt = service.build_system_prompt(
            CommunicationType.BEHAVIOR,
            "NRW",
            CommunicationTone.PROFESSIONAL,
        )

        # Prüfe Großbuchstaben-Varianten (wie im Prompt verwendet)
        assert "BEOBACHTUNG" in prompt
        assert "GEFÜHLE" in prompt
        assert "BEDÜRFNISSE" in prompt
        assert "BITTEN" in prompt

    def test_build_system_prompt_contains_tone(self):
        """Test System-Prompt enthält Tonalität."""
        service = CommunicationService()
        prompt = service.build_system_prompt(
            CommunicationType.BEHAVIOR,
            "NRW",
            CommunicationTone.WARM,
        )

        assert "warmherzig" in prompt.lower()


class TestBuildUserPrompt:
    """Tests für build_user_prompt Methode."""

    def test_build_user_prompt_with_context(self):
        """Test User-Prompt mit Kontext."""
        service = CommunicationService()
        prompt = service.build_user_prompt(
            CommunicationType.BEHAVIOR,
            {
                "student_name": "Max",
                "parent_name": "Frau Müller",
                "situation": "Max stört häufig den Unterricht.",
                "additional_info": "Bereits 3x ermahnt.",
            },
        )

        assert "Max" in prompt
        assert "Frau Müller" in prompt
        assert "stört" in prompt
        assert "ermahnt" in prompt


class TestValidateCommunication:
    """Tests für validate_communication Methode."""

    def test_validate_good_communication(self):
        """Test Validierung einer guten Kommunikation."""
        service = CommunicationService()
        text = """
        Sehr geehrte Frau Müller,

        ich habe bemerkt, dass Max in letzter Zeit häufiger abwesend war.
        Ich möchte Sie gerne zu einem Gespräch einladen, da mir eine gute
        Zusammenarbeit sehr wichtig ist.

        Wären Sie bereit, nächste Woche zu einem Termin zu kommen?

        Mit freundlichen Grüßen
        """

        result = service.validate_communication(text)

        assert result["is_valid"] is True
        assert len(result["positive_elements"]) > 0
        assert result["gfk_score"] > 0.5

    def test_validate_bad_communication(self):
        """Test Validierung einer problematischen Kommunikation."""
        service = CommunicationService()
        text = """
        Sehr geehrte Frau Müller,

        Sie müssen endlich etwas tun! Das Kind ist faul und respektlos.
        Sie sollten mehr kontrollieren.
        """

        result = service.validate_communication(text)

        assert result["is_valid"] is False
        assert len(result["issues"]) > 0
        assert len(result["suggestions"]) > 0


class TestGetAllCommunicationTypes:
    """Tests für get_all_communication_types Methode."""

    def test_returns_all_types(self):
        """Test gibt alle Typen zurück."""
        service = CommunicationService()
        types = service.get_all_communication_types()

        assert len(types) == 9
        assert all("value" in t and "label" in t for t in types)


class TestGetAllTones:
    """Tests für get_all_tones Methode."""

    def test_returns_all_tones(self):
        """Test gibt alle Tonalitäten zurück."""
        service = CommunicationService()
        tones = service.get_all_tones()

        assert len(tones) == 5
        assert all("value" in t and "label" in t for t in tones)


class TestGetStates:
    """Tests für get_states Methode."""

    def test_returns_all_16_bundeslaender(self):
        """Test gibt alle 16 Bundesländer zurück."""
        service = CommunicationService()
        states = service.get_states()

        assert len(states) == 16
        # Prüfe einige
        state_values = [s["value"] for s in states]
        assert "NRW" in state_values
        assert "BY" in state_values
        assert "BE" in state_values


class TestGetCommunicationService:
    """Tests für Singleton-Pattern."""

    def test_singleton_pattern(self):
        """Test dass get_communication_service immer dieselbe Instanz zurückgibt."""
        service1 = get_communication_service()
        service2 = get_communication_service()

        assert service1 is service2

    def test_returns_communication_service(self):
        """Test dass CommunicationService zurückgegeben wird."""
        service = get_communication_service()

        assert isinstance(service, CommunicationService)
