# #region Test.LLMTranslationService.Extra [C:3] [TYPE Module] [SEMANTICS test, llm, translation, coverage]
# @BRIEF Additional coverage for LLMTranslationService edge cases — remaining uncovered paths.
# @RELATION BINDS_TO -> [LLMTranslationService]
# @TEST_EDGE: undetected_language_triggers_review -> Review flag set on TranslationLanguage
# @TEST_EDGE: recovery_with_und_detected_lang -> Fallback to LLM response field
# @TEST_EDGE: empty_source_text_cache_check -> continue on empty source in _check_cache
# @TEST_EDGE: provider_token_config_error -> Graceful fallback on config error
# @TEST_EDGE: backward_compat_delegation -> call_openai_compatible / _parse_llm_response wrappers
import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from src.models.translate import TranslationLanguage, TranslationRecord
from src.plugins.translate._llm_call import LLMTranslationService, MAX_RETRIES_PER_BATCH

from .conftest import JOB_ID, RUN_ID


def _make_job(session, provider_id="test-provider", target_langs=None):
    from src.models.translate import TranslationJob
    job = session.query(TranslationJob).filter(TranslationJob.id == JOB_ID).first()
    if job is None:
        job = TranslationJob(
            id=JOB_ID, name="LLM Extra Test", source_dialect="en",
            target_dialect=target_langs[0] if target_langs else "fr",
            target_languages=target_langs or ["fr"],
            status="ACTIVE", provider_id=provider_id,
        )
        session.add(job)
    else:
        if provider_id:
            job.provider_id = provider_id
        if target_langs:
            job.target_languages = target_langs
            job.target_dialect = target_langs[0]
    session.commit()
    return job


def _batch_rows(count=2, detected_lang="en"):
    return [
        {
            "row_index": str(i),
            "source_text": f"Hello world {i}",
            "source_data": {"table": "test"},
            "_detected_lang": detected_lang,
        }
        for i in range(count)
    ]


class TestCreateRecordsUndetectedLang:
    """Cover _create_records_from_translations with undetected language (needs_review=True)."""

    def test_undetected_lang_triggers_needs_review(self, db_with_run):
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="und")
        translations = {"0": {"fr": "Bonjour", "translation": "Bonjour"}}
        result = svc._create_records_from_translations(
            rows, run_id, "batch-1", ["fr"], translations, [], 0,
        )
        assert result["successful"] == 1
        langs = session.query(TranslationLanguage).all()
        assert len(langs) == 1
        assert langs[0].needs_review is True

    def test_detected_lang_same_as_target_lang_skipped(self, db_with_run):
        """When detected_lang matches target language code, skip that language row."""
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="fr")
        translations = {"0": {"fr": "Bonjour", "translation": "Bonjour", "de": "Hallo"}}
        result = svc._create_records_from_translations(
            rows, run_id, "batch-1", ["fr", "de"], translations, [], 0,
        )
        assert result["successful"] == 1
        langs = session.query(TranslationLanguage).all()
        lang_codes = [l.language_code for l in langs]
        assert "fr" not in lang_codes  # skipped because detected_lang == "fr"
        assert "de" in lang_codes

    def test_detected_lang_fallback_to_llm_response(self, db_with_run):
        """When _detected_lang is 'und', fall back to LLM response field."""
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="und")
        translations = {
            "0": {
                "fr": "Bonjour",
                "translation": "Bonjour",
                "detected_source_language": "en",
            }
        }
        result = svc._create_records_from_translations(
            rows, run_id, "batch-1", ["fr"], translations, [], 0,
        )
        assert result["successful"] == 1
        langs = session.query(TranslationLanguage).all()
        assert langs[0].source_language_detected == "en"


class TestRecoveryUndetectedLang:
    """Cover _try_recover_partial with undetected language paths."""

    def test_recovery_with_und_detected_lang(self, db_with_run):
        """Recovery with _detected_lang='und' falls back to LLM field."""
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="und")
        translations_response = json.dumps([{"row_id": "0", "fr": "Bonjour"}])

        with patch('src.plugins.translate._llm_call.parse_llm_response',
                   return_value={"0": {"fr": "Bonjour", "detected_source_language": "fr"}}):
            recovered = svc._try_recover_partial(
                translations_response, rows, run_id, "batch-1", ["fr"],
            )
            assert recovered == {"0"}
            # When detected_lang="und" and response has detected_source_language="fr",
            # detected_lang is set to "fr" — but target_languages=["fr"] so no
            # TranslationLanguage is created (same-lang skip). Only TranslationRecord exists.
            records = session.query(TranslationRecord).all()
            assert len(records) == 1

    def test_recovery_needs_review(self, db_with_run):
        """Recovery with undetected lang sets needs_review=True."""
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="und")
        translations_response = json.dumps([{"row_id": "0", "fr": "Bonjour"}])

        with patch('src.plugins.translate._llm_call.parse_llm_response',
                   return_value={"0": {"fr": "Bonjour", "detected_source_language": "und"}}):
            recovered = svc._try_recover_partial(
                translations_response, rows, run_id, "batch-1", ["fr"],
            )
            assert recovered == {"0"}
            langs = session.query(TranslationLanguage).all()
            assert langs[0].needs_review is True


class TestBackwardCompatDelegation:
    """Cover backward-compatibility static wrapper methods."""

    @pytest.mark.asyncio
    async def test_call_openai_compatible_delegates(self):
        with patch('src.plugins.translate._llm_call.call_openai_compatible',
                   new=AsyncMock(return_value=("response", "stop"))):
            result = await LLMTranslationService.call_openai_compatible(
                "url", "key", "model", "prompt"
            )
            assert result == ("response", "stop")

    def test_parse_llm_response_delegates(self):
        with patch('src.plugins.translate._llm_call.parse_llm_response',
                   return_value={"0": {"fr": "test"}}):
            result = LLMTranslationService._parse_llm_response(
                '{"fr": "test"}', 1, ["fr"]
            )
            assert result == {"0": {"fr": "test"}}


class TestCreateRecordsEnforceDict:
    """Cover dictionary enforcement with dict_matches."""

    def test_dict_enforcement_called(self, db_with_run):
        """_enforce_dictionary is called when dict_matches and source_text exist."""
        session, run_id = db_with_run
        svc = LLMTranslationService(session)
        rows = _batch_rows(1, detected_lang="und")
        translations = {"0": {"fr": "Bonjour", "translation": "Bonjour"}}
        dict_matches = [{"entry_id": "e1", "source_term": "hello", "target_term": "hola"}]

        with patch('src.plugins.translate._llm_call._enforce_dictionary') as mock_enf:
            result = svc._create_records_from_translations(
                rows, run_id, "batch-1", ["fr"], translations, dict_matches, 0,
            )
            assert result["successful"] == 1
            mock_enf.assert_called_once()

    def test_plv_fallback_to_translation_key(self, db_session):
        """When per-language values are empty but translation key exists."""
        td = {"translation": "Bonjour le monde"}
        result = LLMTranslationService._extract_per_lang_values(td, ["fr"])
        assert result == {"fr": "Bonjour le monde"}
# #endregion Test.LLMTranslationService.Extra
