"""
Tests du garde-fou d'écriture (wrong-script retry).

Scénario principal : un LLM livre de l'arabe alors que la cible est le
persan — même écriture, mauvaise langue, indétectable à l'œil d'un
non-lecteur. Le détecteur doit le signaler et le traducteur doit
redemander une fois avec une consigne renforcée, sans jamais risquer
de dégrader le document (la 2e tentative ne remplace que si elle passe
le même contrôle).
"""

from docx import Document

from services.providers.base import TranslationProvider
from services.providers.schemas import TranslationRequest, TranslationResponse
from services.quality.script_detector import script_issue
from translators.segments import retry_wrong_script
from translators.word_translator import WordTranslator

# Arabe pur (aucune lettre persane spécifique), bien plus de 25 lettres.
ARABIC_FOR_PERSIAN = "مرحبا بكم في صفحة المشاريع الجديدة الخاصة بنا"
# Persan correct : contient پ et ژ (lettres spécifiques du persan).
GOOD_PERSIAN = "به صفحه پروژه های جدید ما خوش آمدید"
SOURCE_EN = "Welcome to our new projects page"


class SequencedMockProvider(TranslationProvider):
    """Renvoie une mauvaise traduction, puis la bonne dès que la requête
    porte la consigne de retentative (détectée via le custom prompt)."""

    def __init__(self, wrong: dict, corrected: dict):
        self._wrong = wrong
        self._corrected = corrected
        self.calls = []  # (texte, porte_la_consigne_de_retry)

    def get_name(self) -> str:
        return "sequenced-mock"

    def is_available(self) -> bool:
        return True

    def translate_text(self, request: TranslationRequest) -> TranslationResponse:
        custom = (request.metadata or {}).get("custom_prompt") or ""
        is_retry = "CRITICAL RETRY" in custom
        self.calls.append((request.text, is_retry))
        table = self._corrected if is_retry else self._wrong
        return TranslationResponse(
            translated_text=table.get(request.text, request.text),
            provider_name="sequenced-mock",
            source_language=request.source_language,
        )

    def translate_batch(self, requests: list) -> list:
        return [self.translate_text(req) for req in requests]


# ---------------------------------------------------------------------------
# script_issue — détection seule
# ---------------------------------------------------------------------------


class TestScriptIssue:
    def test_pure_arabic_for_persian_flagged(self):
        reason = script_issue(ARABIC_FOR_PERSIAN, "fa")
        assert reason is not None
        assert "fa" in reason

    def test_correct_persian_passes(self):
        assert script_issue(GOOD_PERSIAN, "fa") is None

    def test_latin_for_persian_flagged(self):
        assert script_issue("Bienvenue sur notre page", "fa") is not None

    def test_cyrillic_for_french_flagged(self):
        reason = script_issue("Добро пожаловать на новую страницу", "fr")
        assert reason is not None

    def test_short_pure_arabic_stays_tolerated(self):
        # Texte court : pas assez de signal pour distinguer arabe et
        # persan sans lettre discriminante — comportement historique.
        assert script_issue("السلام عليكم", "fa") is None

    def test_identity_and_empty_are_never_flagged(self):
        assert script_issue("", "fa") is None
        assert script_issue("2024", "fa") is None


# ---------------------------------------------------------------------------
# retry_wrong_script — mécanisme seul
# ---------------------------------------------------------------------------


class TestRetryWrongScript:
    def test_retry_replaces_with_valid_translation(self):
        def translate_fn(texts, hint):
            assert "CRITICAL RETRY" in hint
            assert texts == [SOURCE_EN]
            return [GOOD_PERSIAN]

        result, fixed = retry_wrong_script(
            [SOURCE_EN], [ARABIC_FOR_PERSIAN], "fa", translate_fn
        )
        assert fixed == 1
        assert result[0] == GOOD_PERSIAN

    def test_keeps_first_attempt_when_retry_still_wrong(self):
        def translate_fn(texts, hint):
            return [ARABIC_FOR_PERSIAN]  # la 2e tentative est tout aussi fausse

        result, fixed = retry_wrong_script(
            [SOURCE_EN], [ARABIC_FOR_PERSIAN], "fa", translate_fn
        )
        assert fixed == 0
        assert result[0] == ARABIC_FOR_PERSIAN

    def test_identity_output_never_triggers_retry(self):
        def translate_fn(texts, hint):  # pragma: no cover — ne doit pas servir
            raise AssertionError("no retry expected for identity output")

        result, fixed = retry_wrong_script(
            ["Microsoft 2024"], ["Microsoft 2024"], "fa", translate_fn
        )
        assert fixed == 0
        assert result == ["Microsoft 2024"]

    def test_correct_translation_never_triggers_retry(self):
        def translate_fn(texts, hint):  # pragma: no cover
            raise AssertionError("no retry expected for a correct translation")

        result, fixed = retry_wrong_script(
            [SOURCE_EN], [GOOD_PERSIAN], "fa", translate_fn
        )
        assert fixed == 0
        assert result == [GOOD_PERSIAN]

    def test_provider_error_keeps_originals(self):
        def translate_fn(texts, hint):
            raise RuntimeError("boom")

        result, fixed = retry_wrong_script(
            [SOURCE_EN], [ARABIC_FOR_PERSIAN], "fa", translate_fn
        )
        assert fixed == 0
        assert result == [ARABIC_FOR_PERSIAN]

    def test_only_offenders_are_resent(self):
        sent = []

        def translate_fn(texts, hint):
            sent.extend(texts)
            return [GOOD_PERSIAN] * len(texts)

        sources = [SOURCE_EN, "Second text", "Third text"]
        translations = [ARABIC_FOR_PERSIAN, "متنی که کاملاً درست است", "سلام دوست من"]
        # La 2e contient ک ? non — vérifions plutôt avec un persan valide
        # pour montrer que seules les traductions fautives repartent.
        translations[1] = "این متن کاملاً درست است"
        translations[2] = "این هم درست است"

        result, fixed = retry_wrong_script(sources, translations, "fa", translate_fn)
        assert sent == [SOURCE_EN]
        assert fixed == 1


# ---------------------------------------------------------------------------
# Intégration — le traducteur Word corrige un lot entier
# ---------------------------------------------------------------------------


class TestWordIntegration:
    def test_word_retries_arabic_delivered_for_persian(self, tmp_path):
        provider = SequencedMockProvider(
            {SOURCE_EN: ARABIC_FOR_PERSIAN},
            {SOURCE_EN: GOOD_PERSIAN},
        )
        translator = WordTranslator(provider=provider)

        doc = Document()
        doc.add_paragraph(SOURCE_EN)
        input_file = tmp_path / "input.docx"
        doc.save(input_file)

        output_file = tmp_path / "output.docx"
        translator.translate_file(input_file, output_file, "fa")

        # Une demande initiale + une retentative avec consigne renforcée
        non_retry = [c for c in provider.calls if not c[1]]
        retry = [c for c in provider.calls if c[1]]
        assert len(non_retry) == 1
        assert len(retry) == 1
        assert retry[0][0] == SOURCE_EN

        doc_out = Document(output_file)
        assert GOOD_PERSIAN in doc_out.paragraphs[0].text

    def test_word_no_retry_for_latin_target(self, tmp_path):
        provider = SequencedMockProvider(
            {SOURCE_EN: "Bienvenue sur notre page"}, {}
        )
        translator = WordTranslator(provider=provider)

        doc = Document()
        doc.add_paragraph(SOURCE_EN)
        input_file = tmp_path / "input.docx"
        doc.save(input_file)

        output_file = tmp_path / "output.docx"
        translator.translate_file(input_file, output_file, "fr")

        # Traduction latine correcte pour une cible latine : une seule demande
        assert len(provider.calls) == 1
        assert not provider.calls[0][1]
