# #region TestDictionaryCRUD [C:3] [TYPE Module] [SEMANTICS test, dictionary, crud]
# @BRIEF Validate DictionaryCRUD and DictionaryEntryCRUD operations.
# @RELATION BINDS_TO -> [DictionaryCRUD]
# @RELATION BINDS_TO -> [DictionaryEntryCRUD]
# @TEST_EDGE  duplicate_entry -> ValueError on repeated (dictionary_id, source_term_norm, source_lang, target_lang)
# @TEST_EDGE  delete_active_job -> ValueError with active/scheduled message
# @TEST_EDGE  same_term_different_lang_pair -> allowed (not duplicate)
# @TEST_INVARIANT  unique_normalized -> verifies: [duplicate_entry, same_term_different_lang_pair allowed]

import pytest

from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session, sessionmaker

from src.models.translate import Base, TranslationJob, TranslationJobDictionary
from src.plugins.translate.dictionary import DictionaryManager


# region db_session [TYPE Fixture]
# @PURPOSE: Provide an in-memory SQLite session for each test.
@pytest.fixture
def db_session():
    engine = create_engine("sqlite:///:memory:", echo=False)

    @event.listens_for(engine, "connect")
    def _set_sqlite_pragma(dbapi_connection, connection_record):
        cursor = dbapi_connection.cursor()
        cursor.execute("PRAGMA foreign_keys=ON")
        cursor.close()

    Base.metadata.create_all(bind=engine)
    session = sessionmaker(bind=engine)()
    try:
        yield session
    finally:
        session.close()
        Base.metadata.drop_all(bind=engine)
# endregion db_session


class TestDictionaryCRUD:
    """Verify dictionary-level CRUD operations."""

    # region test_create_dictionary [C:2] [TYPE Function]
    # @BRIEF  Verify dictionary creation and read-back.
    def test_create_dictionary(self, db_session: Session):
        d = DictionaryManager.create_dictionary(
            db_session, name="Finance Terms",
            source_dialect="postgresql", target_dialect="clickhouse",
            created_by="test_user", description="Finance-related term mappings",
        )
        assert d.id is not None
        assert d.name == "Finance Terms"
        assert d.source_dialect == "postgresql"
        assert d.target_dialect == "clickhouse"
        assert d.created_by == "test_user"
        assert d.is_active is True

        fetched = DictionaryManager.get_dictionary(db_session, d.id)
        assert fetched.id == d.id
        assert fetched.name == "Finance Terms"
    # endregion test_create_dictionary

    # region test_update_dictionary [C:2] [TYPE Function]
    # @BRIEF  Verify dictionary metadata update.
    def test_update_dictionary(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Old Name", source_dialect="a", target_dialect="b")
        updated = DictionaryManager.update_dictionary(
            db_session, d.id, name="New Name", description="Updated desc", is_active=False,
        )
        assert updated.name == "New Name"
        assert updated.description == "Updated desc"
        assert updated.is_active is False
    # endregion test_update_dictionary

    # region test_delete_dictionary [C:2] [TYPE Function]
    # @BRIEF  Verify dictionary deletion also removes entries.
    def test_delete_dictionary(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="To Delete", source_dialect="a", target_dialect="b")
        entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es")
        assert entry.id is not None
        DictionaryManager.delete_dictionary(db_session, d.id)
        with pytest.raises(ValueError, match="Dictionary not found"):
            DictionaryManager.get_dictionary(db_session, d.id)
    # endregion test_delete_dictionary

    # region test_list_dictionaries [C:2] [TYPE Function]
    # @BRIEF  Verify paginated dictionary listing.
    def test_list_dictionaries(self, db_session: Session):
        for i in range(5):
            DictionaryManager.create_dictionary(db_session, name=f"Dict {i}", source_dialect="a", target_dialect="b")
        dicts, total = DictionaryManager.list_dictionaries(db_session, page=1, page_size=2)
        assert total == 5
        assert len(dicts) == 2
    # endregion test_list_dictionaries

    # region test_delete_dictionary_blocked_by_active_job [C:2] [TYPE Function]
    # @BRIEF  Verify deletion is blocked when attached to active/scheduled jobs.
    def test_delete_dictionary_blocked_by_active_job(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        job = TranslationJob(name="Active Job", source_dialect="a", target_dialect="b", status="ACTIVE", created_by="test_user")
        db_session.add(job)
        db_session.flush()
        link = TranslationJobDictionary(job_id=job.id, dictionary_id=d.id)
        db_session.add(link)
        db_session.commit()
        with pytest.raises(ValueError, match="active/scheduled"):
            DictionaryManager.delete_dictionary(db_session, d.id)
    # endregion test_delete_dictionary_blocked_by_active_job

    # region test_delete_dictionary_allowed_with_completed_job [C:2] [TYPE Function]
    # @BRIEF  Verify deletion is allowed when only completed/failed jobs reference the dictionary.
    def test_delete_dictionary_allowed_with_completed_job(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        job = TranslationJob(name="Completed Job", source_dialect="a", target_dialect="b", status="COMPLETED", created_by="test_user")
        db_session.add(job)
        db_session.flush()
        link = TranslationJobDictionary(job_id=job.id, dictionary_id=d.id)
        db_session.add(link)
        db_session.commit()
        DictionaryManager.delete_dictionary(db_session, d.id)
        with pytest.raises(ValueError, match="Dictionary not found"):
            DictionaryManager.get_dictionary(db_session, d.id)
    # endregion test_delete_dictionary_allowed_with_completed_job


class TestDictionaryEntryCRUD:
    """Verify entry-level CRUD operations."""

    # region test_add_entry_duplicate [C:2] [TYPE Function]
    # @BRIEF  Verify duplicate entry raises ValueError.
    def test_add_entry_duplicate(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        DictionaryManager.add_entry(db_session, d.id, "Hello", "Hola", source_language="en", target_language="es")
        with pytest.raises(ValueError, match="already exists"):
            DictionaryManager.add_entry(db_session, d.id, "hello", "Bonjour", source_language="en", target_language="es")
        with pytest.raises(ValueError, match="already exists"):
            DictionaryManager.add_entry(db_session, d.id, "HELLO", "Ciao", source_language="en", target_language="es")
    # endregion test_add_entry_duplicate

    # region test_add_entry_duplicate_per_dictionary [C:2] [TYPE Function]
    # @BRIEF  Verify duplicate is per-dictionary (same term in different dicts is OK).
    def test_add_entry_duplicate_per_dictionary(self, db_session: Session):
        d1 = DictionaryManager.create_dictionary(db_session, name="Dict1", source_dialect="a", target_dialect="b")
        d2 = DictionaryManager.create_dictionary(db_session, name="Dict2", source_dialect="a", target_dialect="b")
        DictionaryManager.add_entry(db_session, d1.id, "hello", "hola", source_language="en", target_language="es")
        entry = DictionaryManager.add_entry(db_session, d2.id, "hello", "bonjour", source_language="en", target_language="es")
        assert entry.id is not None
    # endregion test_add_entry_duplicate_per_dictionary

    # region test_edit_entry [C:2] [TYPE Function]
    # @BRIEF  Verify entry edit updates fields and enforces uniqueness.
    def test_edit_entry(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es")
        updated = DictionaryManager.edit_entry(db_session, entry.id, target_term="HOLA!")
        assert updated.target_term == "HOLA!"
        DictionaryManager.add_entry(db_session, d.id, "world", "mundo", source_language="en", target_language="es")
        with pytest.raises(ValueError, match="already exists"):
            DictionaryManager.edit_entry(db_session, entry.id, source_term="WORLD")
    # endregion test_edit_entry

    # region test_delete_entry [C:2] [TYPE Function]
    # @BRIEF  Verify entry deletion.
    def test_delete_entry(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        entry = DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es")
        DictionaryManager.delete_entry(db_session, entry.id)
        entries, total = DictionaryManager.list_entries(db_session, d.id)
        assert total == 0
    # endregion test_delete_entry

    # region test_clear_entries [C:2] [TYPE Function]
    # @BRIEF  Verify clearing all entries for a dictionary.
    def test_clear_entries(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Test", source_dialect="a", target_dialect="b")
        DictionaryManager.add_entry(db_session, d.id, "hello", "hola", source_language="en", target_language="es")
        DictionaryManager.add_entry(db_session, d.id, "world", "mundo", source_language="en", target_language="es")
        deleted = DictionaryManager.clear_entries(db_session, d.id)
        assert deleted == 2
        entries, total = DictionaryManager.list_entries(db_session, d.id)
        assert total == 0
    # endregion test_clear_entries

    # region test_add_entry_with_language_pair [C:2] [TYPE Function]
    # @BRIEF  Verify creating entry with language pair stores correctly.
    def test_add_entry_with_language_pair(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Lang Test", source_dialect="a", target_dialect="b")
        entry = DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru")
        assert entry.source_language == "en"
        assert entry.target_language == "ru"
        entries, total = DictionaryManager.list_entries(db_session, d.id)
        assert total == 1
        assert entries[0].source_language == "en"
        assert entries[0].target_language == "ru"
    # endregion test_add_entry_with_language_pair

    # region test_duplicate_same_language_pair [C:2] [TYPE Function]
    # @BRIEF  Verify duplicate with same language pair raises conflict.
    def test_duplicate_same_language_pair(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Dup Test", source_dialect="a", target_dialect="b")
        DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru")
        with pytest.raises(ValueError, match="already exists"):
            DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="ru")
    # endregion test_duplicate_same_language_pair

    # region test_same_term_different_language_pair [C:2] [TYPE Function]
    # @BRIEF  Verify same term with different language pair is allowed.
    def test_same_term_different_language_pair(self, db_session: Session):
        d = DictionaryManager.create_dictionary(db_session, name="Multi Lang", source_dialect="a", target_dialect="b")
        entry1 = DictionaryManager.add_entry(db_session, d.id, "hello", "привет", source_language="en", target_language="ru")
        entry2 = DictionaryManager.add_entry(db_session, d.id, "hello", "hallo", source_language="en", target_language="de")
        assert entry1.id != entry2.id
        assert entry1.target_language == "ru"
        assert entry2.target_language == "de"
        entries, total = DictionaryManager.list_entries(db_session, d.id)
        assert total == 2
    # endregion test_same_term_different_language_pair
# #endregion TestDictionaryCRUD
