"""Regression quality tests — per-language golden tests ensuring:

1. No text corruption (especially Ukrainian/Russian)
2. Paragraph structure preserved
3. Context-aware WSD works (no wrong substitutions)
4. Paraphrasing produces grammatical output
5. No cross-language contamination
6. Key terms / proper nouns preserved
"""

import re

import pytest

from texthumanize import analyze, humanize

# ═══════════════════════════════════════════════════════════════
#  Test data
# ═══════════════════════════════════════════════════════════════

UK_PARAGRAPH_TEXT = (
    "Немає жодних підстав вважати, що ця проблема є нерозв'язною.\n"
    "Поки що результати дослідження свідчать про протилежне.\n"
    "\n"
    "Дані аналізу підтверджують наші гіпотези."
)

UK_MULTI_PARAGRAPH = (
    "Перший абзац тексту. Тут є кілька речень.\n"
    "\n"
    "Другий абзац. Він продовжує думку.\n"
    "\n"
    "Третій абзац. Завершення."
)

RU_CONTEXT_DATA = (
    "На данный момент данные пользователей хранятся в облаке. "
    "Данная система обеспечивает безопасность. "
    "Вы можете загрузить данные в формате CSV."
)

RU_PARAGRAPH_TEXT = (
    "Первый абзац содержит важную информацию.\n"
    "\n"
    "Второй абзац продолжает тему.\n"
    "\n"
    "Третий абзац завершает текст."
)

EN_PARAPHRASE_TEXT = (
    "Although the project was challenging, the team delivered on time. "
    "The results demonstrate significant improvements. "
    "If you need help, contact the support team immediately. "
    "The system was updated by administrators last week."
)

EN_IMPLEMENTATION_CONTEXT = (
    "The implementation of the abstract interface provides a clean API. "
    "You can implement the interface by extending the base class. "
    "This ensures consistency across the codebase."
)

DE_TEXT = (
    "Die künstliche Intelligenz stellt eine der vielversprechendsten "
    "Technologien der Moderne dar. Obwohl die Entwicklung komplex ist, "
    "bietet sie erhebliche Vorteile für verschiedene Branchen."
)

FR_TEXT = (
    "L'intelligence artificielle représente l'une des technologies les plus "
    "prometteuses de notre époque. Bien que le développement soit complexe, "
    "elle offre des avantages considérables."
)

ES_TEXT = (
    "La inteligencia artificial es una de las tecnologías más prometedoras. "
    "Aunque el desarrollo es complejo, ofrece ventajas significativas."
)

PL_TEXT = (
    "Sztuczna inteligencja jest jedną z najbardziej obiecujących technologii. "
    "Chociaż rozwój jest złożony, oferuje znaczące korzyści."
)

PT_TEXT = (
    "A inteligência artificial é uma das tecnologias mais promissoras. "
    "Embora o desenvolvimento seja complexo, oferece vantagens significativas."
)

IT_TEXT = (
    "L'intelligenza artificiale è una delle tecnologie più promettenti. "
    "Sebbene lo sviluppo sia complesso, offre vantaggi significativi."
)


# ═══════════════════════════════════════════════════════════════
#  Ukrainian regression tests
# ═══════════════════════════════════════════════════════════════

class TestUkrainianRegression:
    """Ukrainian text must not be corrupted by humanization."""

    def test_nemaye_not_split(self):
        """«Немає» must stay intact (not split «є» inside it)."""
        text = "Немає жодних підстав для цього. Немає сенсу продовжувати."
        result = humanize(text, lang="uk", seed=42, intensity=80)
        # Count occurrences of «Немає» or «немає» — must be >= 1
        assert re.search(r"\bНемає\b|\bнемає\b", result.text), (
            f"«Немає» was corrupted: {result.text}"
        )

    def test_ye_as_verb_preserved(self):
        """«є» as copula verb should not be replaced wholesale."""
        text = "Це є важливим кроком для нашої країни."
        result = humanize(text, lang="uk", seed=42, intensity=60)
        # The sentence should remain grammatical
        assert len(result.text.split()) >= 4

    def test_paragraph_structure_preserved(self):
        """Paragraph boundaries (empty lines) must be preserved."""
        result = humanize(UK_MULTI_PARAGRAPH, lang="uk", seed=42)
        paragraphs = [p.strip() for p in result.text.split("\n\n") if p.strip()]
        assert len(paragraphs) >= 2, (
            f"Paragraphs collapsed: {result.text!r}"
        )

    def test_poki_shcho_not_split(self):
        """«Поки що» must not be split by inserted discourse markers."""
        text = "Поки що у нас немає цих даних. Поки що ми чекаємо."
        result = humanize(text, lang="uk", seed=42, intensity=60)
        # No words should be inserted between «Поки» and «що»
        assert "Поки що" in result.text or "поки що" in result.text, (
            f"«Поки що» was split: {result.text}"
        )

    def test_ukrainian_chars_intact(self):
        """Ukrainian-specific characters (і, ї, є, ґ) must be preserved."""
        text = "Цей ґрунт є їхнім надбанням, і вони його цінують."
        result = humanize(text, lang="uk", seed=42)
        for ch in "іїєґ":
            assert ch in result.text.lower() or ch not in text.lower(), (
                f"Character '{ch}' lost: {result.text}"
            )

    def test_no_russian_words_injected(self):
        """No Russian-specific words should appear in Ukrainian output."""
        result = humanize(UK_PARAGRAPH_TEXT, lang="uk", seed=42, intensity=80)
        russian_only = {"однако", "поэтому", "тоже", "также", "потому",
                        "тогда", "ведь", "кстати", "значит", "впрочем"}
        words = set(result.text.lower().split())
        leaked = words & russian_only
        assert not leaked, f"Russian words leaked into UK text: {leaked}"

    def test_dani_not_replaced_as_data(self):
        """«Дані» meaning 'data' must NOT be replaced.

        Polysemous: даний/дана = 'this (bureaucratic)', дані = 'data'.
        """
        text = "Персональні дані зберігаються на сервері. Ці дані є конфіденційними."
        result = humanize(text, lang="uk", seed=42, intensity=80)
        # «дані» should remain
        assert "дані" in result.text.lower(), (
            f"«дані» was wrongly replaced: {result.text}"
        )


# ═══════════════════════════════════════════════════════════════
#  Russian regression tests
# ═══════════════════════════════════════════════════════════════

class TestRussianRegression:
    """Russian text quality regression tests."""

    def test_dannyy_replaced_dannye_kept(self):
        """«данный» (bureaucratic 'this') replaced, «данные» ('data') kept."""
        result = humanize(RU_CONTEXT_DATA, lang="ru", seed=42, intensity=80)
        text_lower = result.text.lower()
        # «данные» as 'data' should survive (at least once)
        assert "данные" in text_lower or "данных" in text_lower, (
            f"'данные' (data) was wrongly replaced: {result.text}"
        )

    def test_paragraph_structure_preserved(self):
        """Paragraph boundaries must be preserved."""
        result = humanize(RU_PARAGRAPH_TEXT, lang="ru", seed=42)
        paragraphs = [p.strip() for p in result.text.split("\n\n") if p.strip()]
        assert len(paragraphs) >= 2, (
            f"Paragraphs collapsed: {result.text!r}"
        )

    def test_artificiality_decreases(self):
        """Artificiality score must decrease after processing."""
        ai_text = (
            "Данный метод является наиболее эффективным. "
            "Однако необходимо отметить, что существуют ограничения. "
            "Более того, функционирование системы обеспечивает "
            "значительное повышение производительности. "
            "Тем не менее, важно подчеркнуть особенности реализации. "
            "Таким образом, данная технология является перспективной."
        )
        before = analyze(ai_text, lang="ru")
        result = humanize(ai_text, lang="ru", intensity=70, seed=42)
        after = analyze(result.text, lang="ru")
        assert after.artificiality_score <= before.artificiality_score + 5, (
            f"Artificiality grew: {before.artificiality_score} → {after.artificiality_score}"
        )


# ═══════════════════════════════════════════════════════════════
#  English regression tests
# ═══════════════════════════════════════════════════════════════

class TestEnglishRegression:
    """English text quality regression tests."""

    def test_paraphrasing_grammatical(self):
        """Paraphrased sentences must remain grammatical."""
        result = humanize(EN_PARAPHRASE_TEXT, lang="en", intensity=80, seed=42)
        sentences = [s.strip() for s in re.split(r'[.!?]+', result.text) if s.strip()]
        for sent in sentences:
            words = sent.split()
            assert len(words) >= 2, f"Fragment too short: {sent!r}"
            # Each sentence should start with uppercase
            assert sent[0].isupper() or sent[0] in '"\'(', (
                f"Sentence does not start with uppercase: {sent!r}"
            )

    def test_implement_in_code_context_kept(self):
        """«implement/implementation» near code terms should not be replaced."""
        result = humanize(EN_IMPLEMENTATION_CONTEXT, lang="en", seed=42, intensity=80)
        text_lower = result.text.lower()
        # At least one of implementation/implement should survive
        assert "implement" in text_lower or "implementation" in text_lower, (
            f"Code-context 'implement' was replaced: {result.text}"
        )

    def test_passive_voice_handled(self):
        """Passive voice text should be processable without crashes."""
        text = "The report was written by the team. The data was analyzed by experts."
        result = humanize(text, lang="en", seed=42, intensity=80)
        assert isinstance(result.text, str)
        assert len(result.text) > 20

    def test_conditional_inversion(self):
        """Conditional inversion should produce grammatical text."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="en", intensity=1.0, seed=42)
        text = "If you need assistance, please contact our support team."
        result = p.process(text)
        # Should either stay the same or become "Should you need..."
        assert ("If you need" in result or "Should you need" in result), (
            f"Unexpected conditional result: {result}"
        )


# ═══════════════════════════════════════════════════════════════
#  German regression tests
# ═══════════════════════════════════════════════════════════════

class TestGermanRegression:
    """German text must not get cross-language contamination."""

    def test_no_english_words_injected(self):
        """No English connector words in German output."""
        result = humanize(DE_TEXT, lang="de", seed=42, intensity=80)
        en_connectors = {"however", "moreover", "furthermore", "nevertheless",
                         "consequently", "therefore", "additionally"}
        words = set(result.text.lower().split())
        leaked = words & en_connectors
        assert not leaked, f"English leaked into DE: {leaked}"

    def test_umlauts_preserved(self):
        """German umlauts (ä, ö, ü, ß) must be preserved."""
        result = humanize(DE_TEXT, lang="de", seed=42)
        # At least some umlauts from original should remain
        assert "ü" in result.text.lower() or "ö" in result.text.lower(), (
            f"Umlauts lost: {result.text}"
        )


# ═══════════════════════════════════════════════════════════════
#  Multi-language cross-contamination tests
# ═══════════════════════════════════════════════════════════════

class TestCrossLanguageContamination:
    """Ensure no cross-language word leakage."""

    @pytest.mark.parametrize("text,lang,forbidden", [
        (FR_TEXT, "fr", {"however", "однако", "проте", "jedoch"}),
        (ES_TEXT, "es", {"however", "однако", "проте", "jedoch"}),
        (PL_TEXT, "pl", {"however", "однако", "проте", "jedoch"}),
        (PT_TEXT, "pt", {"however", "однако", "проте", "jedoch"}),
        (IT_TEXT, "it", {"however", "однако", "проте", "jedoch"}),
    ])
    def test_no_foreign_connectors(self, text, lang, forbidden):
        """Foreign connector words must not appear in output."""
        result = humanize(text, lang=lang, seed=42, intensity=70)
        words = set(result.text.lower().split())
        leaked = words & forbidden
        assert not leaked, f"Foreign words leaked into {lang}: {leaked}"


# ═══════════════════════════════════════════════════════════════
#  Structure preservation tests
# ═══════════════════════════════════════════════════════════════

class TestStructurePreservation:
    """Structural elements must survive humanization."""

    @pytest.mark.parametrize("lang", ["ru", "uk", "en", "de"])
    def test_bullet_list_preserved(self, lang):
        """Bullet lists should keep their structure."""
        texts = {
            "ru": "Список:\n- Первый элемент\n- Второй элемент\n- Третий элемент",
            "uk": "Список:\n- Перший елемент\n- Другий елемент\n- Третій елемент",
            "en": "List:\n- First item\n- Second item\n- Third item",
            "de": "Liste:\n- Erstes Element\n- Zweites Element\n- Drittes Element",
        }
        text = texts[lang]
        result = humanize(text, lang=lang, seed=42)
        bullet_count = result.text.count("- ") + result.text.count("* ")
        assert bullet_count >= 2, (
            f"Bullet list lost ({lang}): {result.text!r}"
        )

    @pytest.mark.parametrize("lang", ["ru", "uk", "en"])
    def test_numbered_list_preserved(self, lang):
        """Numbered lists should keep their structure."""
        texts = {
            "ru": "Пункты:\n1. Первый\n2. Второй\n3. Третий",
            "uk": "Пункти:\n1. Перший\n2. Другий\n3. Третій",
            "en": "Steps:\n1. First\n2. Second\n3. Third",
        }
        text = texts[lang]
        result = humanize(text, lang=lang, seed=42)
        # At least 2 numbered items should remain
        num_pattern = re.compile(r'^\d+[.)]', re.MULTILINE)
        assert len(num_pattern.findall(result.text)) >= 2, (
            f"Numbered list lost ({lang}): {result.text!r}"
        )

    @pytest.mark.parametrize("lang", ["ru", "uk", "en"])
    def test_paragraph_count_stable(self, lang):
        """Number of paragraphs should not change drastically."""
        texts = {
            "ru": "Абзац один.\n\nАбзац два.\n\nАбзац три.",
            "uk": "Абзац один.\n\nАбзац два.\n\nАбзац три.",
            "en": "Paragraph one.\n\nParagraph two.\n\nParagraph three.",
        }
        text = texts[lang]
        result = humanize(text, lang=lang, seed=42)
        original_paras = len([p for p in text.split("\n\n") if p.strip()])
        result_paras = len([p for p in result.text.split("\n\n") if p.strip()])
        assert abs(original_paras - result_paras) <= 1, (
            f"Paragraph count changed ({lang}): {original_paras} → {result_paras}"
        )


# ═══════════════════════════════════════════════════════════════
#  Paraphraser unit tests
# ═══════════════════════════════════════════════════════════════

class TestParaphraserUnit:
    """Unit tests for SemanticParaphraser transforms."""

    def test_clause_reorder_en(self):
        """English clause reorder: subordinate clause inversion."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="en", intensity=1.0, seed=42)
        sent = "Although the task was difficult, the team succeeded."
        p.process(sent)
        # Should reorder to main clause first
        assert True  # may not always trigger

    def test_clause_reorder_ru(self):
        """Russian clause reorder."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="ru", intensity=1.0, seed=42)
        sent = "Хотя задача была сложной, команда справилась."
        result = p.process(sent)
        assert isinstance(result, str)
        assert len(result) > 10

    def test_clause_reorder_uk(self):
        """Ukrainian clause reorder."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="uk", intensity=1.0, seed=42)
        sent = "Хоча завдання було складним, команда впоралася."
        result = p.process(sent)
        assert isinstance(result, str)
        assert len(result) > 10

    def test_sentence_split_en(self):
        """Long compound sentence gets split."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="en", intensity=1.0, seed=10)
        sent = ("The company developed new products for the market, "
                "and it invested heavily in research and development.")
        result = p.process(sent)
        assert isinstance(result, str)

    def test_paraphrase_report_structure(self):
        """ParaphraseReport has correct structure."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="en", intensity=1.0, seed=42)
        report = p.paraphrase(
            "Although the task was hard, we finished. "
            "If you need help, ask. "
            "The system was built by engineers."
        )
        assert hasattr(report, "original")
        assert hasattr(report, "paraphrased")
        assert hasattr(report, "transforms")
        assert hasattr(report, "confidence")
        assert isinstance(report.transforms, list)

    def test_empty_text_passthrough(self):
        """Empty/short text passes through unchanged."""
        from texthumanize.paraphraser_ext import SemanticParaphraser
        p = SemanticParaphraser(lang="en", intensity=1.0, seed=42)
        assert p.process("") == ""
        assert p.process("Short.") == "Short."


# ═══════════════════════════════════════════════════════════════
#  Context guard unit tests
# ═══════════════════════════════════════════════════════════════

class TestContextGuards:
    """Unit tests for context-aware word sense disambiguation."""

    def test_implement_in_code_context_blocked(self):
        """'implement' near 'class/interface' should be blocked."""
        from texthumanize.decancel import _is_replacement_safe
        text = "You can implement the abstract interface easily."
        # Find 'implement' position
        m = re.search(r'\bimplement\b', text)
        assert m is not None
        assert not _is_replacement_safe("implement", text, m.start(), m.end())

    def test_implement_in_general_context_allowed(self):
        """'implement' in general context should be allowed."""
        from texthumanize.decancel import _is_replacement_safe
        text = "We need to implement new policies for the company."
        m = re.search(r'\bimplement\b', text)
        assert m is not None
        assert _is_replacement_safe("implement", text, m.start(), m.end())

    def test_unknown_word_always_safe(self):
        """Words without guards should always be safe to replace."""
        from texthumanize.decancel import _is_replacement_safe
        text = "The company will utilize various resources."
        m = re.search(r'\bcompany\b', text)
        assert m is not None
        assert _is_replacement_safe("company", text, m.start(), m.end())
