# #region Test.LanguageDetectService [C:2] [TYPE Module] [SEMANTICS test,language,detection]
# @BRIEF Tests for _lang_detect.py — language detection.
# @RELATION BINDS_TO -> [LanguageDetectService]

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))

import pytest
from unittest.mock import MagicMock, patch

from lingua import LanguageDetectorBuilder

from src.plugins.translate._lang_detect import (
    _detector_cache_key,
    build_detector,
    get_detector,
    detect_language,
    _character_block_fallback,
    batch_detect,
)


class TestDetectorCacheKey:
    """_detector_cache_key."""

    def test_none(self):
        """None returns '__default'."""
        assert _detector_cache_key(None) == "__default"

    def test_empty_list(self):
        """Empty list returns '__default'."""
        assert _detector_cache_key([]) == "__default"

    def test_sorted_key(self):
        """Languages are sorted for deterministic key."""
        key = _detector_cache_key(["fr", "en", "de"])
        assert key == "de,en,fr"

    def test_duplicates(self):
        """Duplicates are removed."""
        key = _detector_cache_key(["en", "en", "fr"])
        assert key == "en,fr"


class TestBuildDetector:
    """build_detector."""

    def test_with_target_languages(self):
        """Builds detector with specific languages."""
        detector = build_detector(["ru", "de"])
        assert detector is not None

    def test_no_target_languages(self):
        """No target languages builds with common source codes."""
        detector = build_detector(None)
        assert detector is not None

    def test_empty_target_languages(self):
        """Empty list builds with common source codes."""
        detector = build_detector([])
        assert detector is not None

    def test_unknown_language_codes(self):
        """Unknown codes are ignored."""
        detector = build_detector(["xx", "yy"])
        # Should try to build; if no lingua_langs found, falls back to all spoken
        assert detector is not None

    def test_fallback_to_all_spoken(self):
        """When _CODE_TO_LANG has no matches, falls back to all spoken languages (line 93)."""
        with patch("src.plugins.translate._lang_detect._CODE_TO_LANG", {}):
            detector = build_detector(["en"])
            assert detector is not None


class TestGetDetector:
    """get_detector — cached detector."""

    def test_cached(self):
        """Same key returns same detector."""
        with patch("src.plugins.translate._lang_detect._detector_cache", {}):
            d1 = get_detector(["en", "fr"])
            d2 = get_detector(["en", "fr"])
            assert d1 is d2

    def test_different_keys(self):
        """Different keys return different detectors."""
        with patch("src.plugins.translate._lang_detect._detector_cache", {}):
            d1 = get_detector(["en"])
            d2 = get_detector(["fr"])
            assert d1 is not d2

    def test_default_key(self):
        """None returns a detector."""
        with patch("src.plugins.translate._lang_detect._detector_cache", {}):
            d = get_detector(None)
            assert d is not None


class TestDetectLanguage:
    """detect_language."""

    def test_empty_text(self):
        """Empty text returns 'und'."""
        detector = build_detector(["en"])
        assert detect_language("", detector) == "und"

    def test_whitespace_only(self):
        """Whitespace-only returns 'und'."""
        detector = build_detector(["en"])
        assert detect_language("   ", detector) == "und"

    def test_english_text(self):
        """English text detected."""
        detector = build_detector(["en", "ru"])
        result = detect_language("Hello, how are you today?", detector)
        assert result == "en"

    def test_russian_text(self):
        """Russian text detected."""
        detector = build_detector(["ru", "en"])
        result = detect_language("Привет, как дела?", detector)
        assert result == "ru"

    def test_exception_returns_und(self):
        """Exception in detection returns 'und'."""
        detector = MagicMock()
        detector.detect_language_of.side_effect = Exception("error")
        result = detect_language("hello", detector)
        assert result == "und"

    def test_none_result(self):
        """None result from detector returns 'und'."""
        detector = MagicMock()
        detector.detect_language_of.return_value = None
        result = detect_language("hello", detector)
        assert result == "und"


class TestCharacterBlockFallback:
    """_character_block_fallback."""

    def test_no_target_languages(self):
        """No target languages returns results unchanged."""
        results = _character_block_fallback(["text"], ["und"], None)
        assert results == ["und"]

    def test_non_cyrillic_targets(self):
        """No Cyrillic targets returns results unchanged."""
        results = _character_block_fallback(["text"], ["und"], ["en", "fr"])
        assert results == ["und"]

    def test_no_und_results(self):
        """No 'und' results returns unchanged."""
        results = _character_block_fallback(["text"], ["en"], ["ru"])
        assert results == ["en"]

    def test_cyrillic_text_detected(self):
        """Cyrillic-dominant text gets assigned ru."""
        results = _character_block_fallback(
            ["привет мир текст"],
            ["und"],
            ["ru", "en"],
        )
        assert results[0] == "ru"

    def test_non_cyrillic_text_stays_und(self):
        """Non-Cyrillic text stays 'und'."""
        results = _character_block_fallback(
            ["hello world 123"],
            ["und"],
            ["ru", "en"],
        )
        assert results[0] == "und"

    def test_empty_text_stays_und(self):
        """Empty text stays 'und'."""
        results = _character_block_fallback(
            [""],
            ["und"],
            ["ru"],
        )
        assert results[0] == "und"


class TestBatchDetect:
    """batch_detect."""

    def test_basic_batch(self):
        """Detects languages for multiple texts."""
        detector = build_detector(["en", "ru", "fr"])
        results = batch_detect(["Hello", "Привет", "Bonjour"],
                                detector=detector)
        assert len(results) == 3

    def test_builds_detector_when_none(self):
        """Builds detector when not provided."""
        results = batch_detect(["Hello"], target_languages=["en"])
        assert len(results) == 1

    def test_empty_texts(self):
        """Empty texts list returns empty list."""
        results = batch_detect([])
        assert results == []

    def test_applies_fallback(self):
        """Cyrillic fallback is applied."""
        results = batch_detect(
            ["привет мир"],
            target_languages=["ru"],
        )
        assert len(results) == 1
# #endregion Test.LanguageDetectService
