# #region Test.DictionaryBatchFilter [C:3] [TYPE Module] [SEMANTICS test, dictionary, filter, batch]
# @BRIEF Verify DictionaryBatchFilter.filter_for_batch — word boundary, regex, language pairs, context, sorting.
# @RELATION BINDS_TO -> [DictionaryBatchFilter]
# @TEST_EDGE: no_dictionaries -> Returns empty list
# @TEST_EDGE: no_entries -> Returns empty list
# @TEST_EDGE: empty_source_text -> Skips gracefully
# @TEST_EDGE: regex_invalid -> Silently skipped
# @TEST_EDGE: context_priority -> Priority flag set when context matches
import pytest
from unittest.mock import MagicMock, patch

from src.models.translate import (
    DictionaryEntry, TerminologyDictionary,
    TranslationJobDictionary,
)
from src.plugins.translate.dictionary_filter import DictionaryBatchFilter

from .conftest import JOB_ID


class TestFilterForBatch:
    """Verify DictionaryBatchFilter.filter_for_batch."""

    def _link_dict(self, session, dict_id):
        """Link dictionary to the base job."""
        link = TranslationJobDictionary(job_id=JOB_ID, dictionary_id=dict_id)
        session.add(link)
        session.commit()

    def test_empty_text_list(self, db_session):
        """Edge: empty source_texts list returns empty."""
        d = TerminologyDictionary(id="dict-empty", name="Test", is_active=True)
        db_session.add(d)
        db_session.flush()
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="entry-1", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hola", source_language="en", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(db_session, [], JOB_ID)
        assert result == []

    def test_no_dictionaries_attached(self, db_session):
        """Negative: no job-dict links returns empty."""
        result = DictionaryBatchFilter.filter_for_batch(db_session, ["hello"], JOB_ID)
        assert result == []

    def test_no_active_dictionaries(self, db_session):
        """Negative: dictionary exists but is not active."""
        d = TerminologyDictionary(id="dict-inactive", name="Inactive", is_active=False)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        result = DictionaryBatchFilter.filter_for_batch(db_session, ["hello"], JOB_ID)
        assert result == []

    def test_no_entries_in_dictionary(self, db_session):
        """Negative: no entries returns empty."""
        d = TerminologyDictionary(id="dict-no-entries", name="Empty", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        result = DictionaryBatchFilter.filter_for_batch(db_session, ["hello"], JOB_ID)
        assert result == []

    def test_basic_matching(self, db_session):
        """Happy: basic case-insensitive word-boundary matching."""
        d = TerminologyDictionary(id="dict-basic", name="Test", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e1", dictionary_id=d.id,
            source_term="Hello", source_term_normalized="hello",
            target_term="Hola", source_language="en", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(db_session, ["Hello World"], JOB_ID)
        assert len(result) == 1
        assert result[0]["source_term"] == "Hello"
        assert result[0]["target_term"] == "Hola"
        assert result[0]["text_index"] == 0

    def test_word_boundary_no_match(self, db_session):
        """Negative: word boundary prevents substring match."""
        d = TerminologyDictionary(id="dict-wb", name="WB", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-cat", dictionary_id=d.id,
            source_term="cat", source_term_normalized="cat",
            target_term="gato", source_language="en", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()
        result = DictionaryBatchFilter.filter_for_batch(db_session, ["catalog"], JOB_ID)
        assert len(result) == 0

    def test_empty_source_text_skipped(self, db_session):
        """Edge: empty source text is skipped."""
        d = TerminologyDictionary(id="dict-emptyskp", name="Test", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e1", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hola", source_language="en", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["hello", "", "world"], JOB_ID
        )
        # Only "hello" matches a dictionary entry; empty string and "world" don't
        assert len(result) == 1
        assert result[0]["text_index"] == 0

    def test_source_language_filter(self, db_session):
        """Edge: filter by source language."""
        d = TerminologyDictionary(id="dict-src", name="Src", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        e1 = DictionaryEntry(
            id="e-en", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hola", source_language="en", target_language="es",
        )
        e2 = DictionaryEntry(
            id="e-fr", dictionary_id=d.id,
            source_term="bonjour", source_term_normalized="bonjour",
            target_term="hola", source_language="fr", target_language="es",
        )
        db_session.add_all([e1, e2])
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["hello bonjour"], JOB_ID, source_language="en",
        )
        assert len(result) == 1
        assert result[0]["source_term"] == "hello"

    def test_target_language_filter(self, db_session):
        """Edge: filter by target language."""
        d = TerminologyDictionary(id="dict-tgt", name="Tgt", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        e1 = DictionaryEntry(
            id="e-es", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hola", source_language="en", target_language="es",
        )
        e2 = DictionaryEntry(
            id="e-de", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hallo", source_language="en", target_language="de",
        )
        db_session.add_all([e1, e2])
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["hello"], JOB_ID, target_language="de",
        )
        assert len(result) == 1
        assert result[0]["target_term"] == "hallo"

    def test_source_language_und_always_matches(self, db_session):
        """Edge: entry with und source matches any source."""
        d = TerminologyDictionary(id="dict-und", name="Und", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-und", dictionary_id=d.id,
            source_term="foo", source_term_normalized="foo",
            target_term="bar", source_language="und", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["foo"], JOB_ID, source_language="de",
        )
        assert len(result) == 1

    def test_regex_matching(self, db_session):
        """Happy: regex entry matches pattern."""
        d = TerminologyDictionary(id="dict-regex1", name="Regex", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-regex", dictionary_id=d.id,
            source_term=r"\d{3}-\d{4}",
            source_term_normalized=r"\d{3}-\d{4}",
            target_term="MASKED", source_language="en", target_language="en",
            is_regex=True,
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["Call 555-1234 now"], JOB_ID
        )
        assert len(result) == 1
        assert result[0]["target_term"] == "MASKED"

    def test_regex_no_match(self, db_session):
        """Negative: regex doesn't match text."""
        d = TerminologyDictionary(id="dict-regex2", name="Regex", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-regex", dictionary_id=d.id,
            source_term=r"\d{5}", source_term_normalized=r"\d{5}",
            target_term="ZIP", source_language="en", target_language="en",
            is_regex=True,
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["hello world"], JOB_ID
        )
        assert len(result) == 0

    def test_regex_invalid_pattern(self, db_session):
        """Edge: invalid regex pattern is silently skipped."""
        d = TerminologyDictionary(id="dict-regex3", name="Bad", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-bad-regex", dictionary_id=d.id,
            source_term=r"[unclosed", source_term_normalized=r"[unclosed",
            target_term="BAD", source_language="en", target_language="en",
            is_regex=True,
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["anything"], JOB_ID
        )
        assert len(result) == 0

    def test_priority_match_with_context(self, db_session):
        """Edge: context-aware priority match."""
        d = TerminologyDictionary(id="dict-ctx", name="Ctx", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-ctx", dictionary_id=d.id,
            source_term="bank", source_term_normalized="bank",
            target_term="banco", source_language="en", target_language="es",
            has_context=True, context_data={"domain": "finance"},
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["bank account"], JOB_ID,
            row_context={"domain": "finance"},
        )
        assert len(result) >= 1

    def test_dictionary_sorting(self, db_session):
        """Edge: results sorted by dictionary link order."""
        d1 = TerminologyDictionary(id="d-p1", name="Priority1", is_active=True)
        d2 = TerminologyDictionary(id="d-p2", name="Priority2", is_active=True)
        db_session.add_all([d1, d2])
        db_session.flush()
        link1 = TranslationJobDictionary(job_id=JOB_ID, dictionary_id=d1.id)
        link2 = TranslationJobDictionary(job_id=JOB_ID, dictionary_id=d2.id)
        db_session.add_all([link1, link2])

        e1 = DictionaryEntry(id="ea", dictionary_id=d1.id,
                             source_term="hello", source_term_normalized="hello",
                             target_term="hola", source_language="en", target_language="es")
        e2 = DictionaryEntry(id="eb", dictionary_id=d2.id,
                             source_term="hello", source_term_normalized="hello",
                             target_term="bonjour", source_language="en", target_language="es")
        db_session.add_all([e1, e2])
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(db_session, ["hello"], JOB_ID)
        assert len(result) == 2
        assert result[0]["dictionary_id"] == d1.id
        assert result[1]["dictionary_id"] == d2.id

    def test_duplicate_matches_deduped(self, db_session):
        """Edge: same (text_idx, entry_id) pair not duplicated."""
        d = TerminologyDictionary(id="dict-dup", name="Dup", is_active=True)
        db_session.add(d)
        self._link_dict(db_session, d.id)
        entry = DictionaryEntry(
            id="e-dup", dictionary_id=d.id,
            source_term="hello", source_term_normalized="hello",
            target_term="hola", source_language="en", target_language="es",
        )
        db_session.add(entry)
        db_session.commit()

        result = DictionaryBatchFilter.filter_for_batch(
            db_session, ["hello hello hello"], JOB_ID,
        )
        assert len(result) == 1
# #endregion Test.DictionaryBatchFilter
