# #region Test.DictionaryImportExport [C:3] [TYPE Module] [SEMANTICS test, dictionary, import, export, csv]
# @BRIEF Verify DictionaryImportExport contracts — import_entries, export_entries, migrate_old_entries.
# @RELATION BINDS_TO -> [DictionaryImportExport]
# @TEST_EDGE: dict_not_found -> Raises ValueError
# @TEST_EDGE: invalid_delimiter -> Raises ValueError
# @TEST_EDGE: missing_required_columns -> Raises ValueError
# @TEST_EDGE: empty_source_or_target -> Skipped with error
# @TEST_EDGE: on_conflict_overwrite -> Updates existing
# @TEST_EDGE: on_conflict_keep_existing -> Skips existing
# @TEST_EDGE: on_conflict_cancel -> Errors on conflict
# @TEST_EDGE: preview_only -> Returns preview without writing
import io
import csv
import pytest

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

from src.models.translate import (
    Base, DictionaryEntry, TerminologyDictionary,
)
from src.plugins.translate.dictionary_import_export import DictionaryImportExport


def make_session():
    engine = create_engine("sqlite:///:memory:")
    @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)()
    return session, engine


def _make_dict(session, name="Test Dict"):
    d = TerminologyDictionary(id="dict-ie", name=name, is_active=True)
    session.add(d)
    session.commit()
    return d


def _csv_content(rows, delimiter=","):
    """Helper: build CSV string from list of dict rows."""
    output = io.StringIO()
    writer = csv.DictWriter(output, fieldnames=["source_term", "target_term", "context_notes",
                                                  "source_language", "target_language"],
                            delimiter=delimiter)
    writer.writeheader()
    for row in rows:
        writer.writerow(row)
    return output.getvalue()


class TestImportEntries:
    """Verify import_entries method."""

    def test_dict_not_found(self):
        """Negative: non-existent dict_id raises ValueError."""
        session, engine = make_session()
        try:
            with pytest.raises(ValueError, match="Dictionary not found"):
                DictionaryImportExport.import_entries(session, "nonexistent", "source,target\nhello,hola")
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_invalid_delimiter(self):
        """Negative: unsupported delimiter."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            with pytest.raises(ValueError, match="Unsupported delimiter"):
                DictionaryImportExport.import_entries(session, d.id, "a|b", delimiter="|")
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_missing_required_columns(self):
        """Negative: CSV missing source_term or target_term."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = "foo,bar\n1,2"
            with pytest.raises(ValueError, match="must have"):
                DictionaryImportExport.import_entries(session, d.id, content, delimiter=",")
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_empty_content_triggers_missing_columns(self):
        """Edge: empty content has no columns."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            with pytest.raises(ValueError, match="must have"):
                DictionaryImportExport.import_entries(session, d.id, "")
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_import_success_csv(self):
        """Happy: basic CSV import creates entries."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "es",
                 "context_notes": "greeting"},
                {"source_term": "world", "target_term": "mundo",
                 "source_language": "en", "target_language": "es",
                 "context_notes": ""},
            ])
            result = DictionaryImportExport.import_entries(session, d.id, content)
            assert result["total"] == 2
            assert result["created"] == 2
            assert result["updated"] == 0
            assert result["skipped"] == 0
            entries = session.query(DictionaryEntry).filter(
                DictionaryEntry.dictionary_id == d.id
            ).all()
            assert len(entries) == 2
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_import_success_tsv(self):
        """Happy: TSV import with auto-detected delimiter."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = "source_term\ttarget_term\nhello\thola\n"
            result = DictionaryImportExport.import_entries(session, d.id, content)
            assert result["created"] == 1
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_empty_source_or_target_skipped(self):
        """Edge: row missing source or target is recorded as error."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = _csv_content([
                {"source_term": "hello", "target_term": ""},
                {"source_term": "", "target_term": "orphan"},
            ])
            result = DictionaryImportExport.import_entries(session, d.id, content)
            assert result["total"] == 2
            assert result["created"] == 0
            assert len(result["errors"]) == 2
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_on_conflict_overwrite(self):
        """Edge: existing entry is updated with on_conflict=overwrite."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            existing = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="bonjour",
                source_language="en", target_language="fr",
            )
            session.add(existing)
            session.commit()

            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "fr"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content, on_conflict="overwrite",
            )
            assert result["updated"] == 1
            assert result["created"] == 0
            session.refresh(existing)
            assert existing.target_term == "hola"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_on_conflict_keep_existing(self):
        """Edge: existing entry kept with on_conflict=keep_existing."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            existing = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="bonjour",
                source_language="en", target_language="fr",
            )
            session.add(existing)
            session.commit()

            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "fr"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content, on_conflict="keep_existing",
            )
            assert result["skipped"] == 1
            assert result["updated"] == 0
            session.refresh(existing)
            assert existing.target_term == "bonjour"  # unchanged
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_on_conflict_cancel(self):
        """Edge: on_conflict=cancel records error."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            existing = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="bonjour",
                source_language="en", target_language="fr",
            )
            session.add(existing)
            session.commit()

            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "fr"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content, on_conflict="cancel",
            )
            assert len(result["errors"]) == 1
            assert "Conflict" in result["errors"][0]["error"]
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_preview_only(self):
        """Edge: preview_only returns preview without writing."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "es"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content, preview_only=True,
            )
            assert len(result["preview"]) == 1
            assert result["preview"][0]["source_term"] == "hello"
            assert result["preview"][0]["is_conflict"] is False
            # No entries should have been created
            count = session.query(DictionaryEntry).count()
            assert count == 0
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_preview_with_existing(self):
        """Edge: preview shows existing as conflict."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            existing = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="bonjour",
                source_language="en", target_language="es",
            )
            session.add(existing)
            session.commit()

            content = _csv_content([
                {"source_term": "hello", "target_term": "hola",
                 "source_language": "en", "target_language": "es"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content, preview_only=True,
            )
            assert result["preview"][0]["is_conflict"] is True
            assert result["preview"][0]["existing_target_term"] == "bonjour"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_default_languages_from_params(self):
        """Edge: default source/target languages applied."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            content = _csv_content([
                {"source_term": "hello", "target_term": "hola"},
            ])
            result = DictionaryImportExport.import_entries(
                session, d.id, content,
                default_source_language="en", default_target_language="es",
            )
            assert result["created"] == 1
            entry = session.query(DictionaryEntry).first()
            assert entry.source_language == "en"
            assert entry.target_language == "es"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)


class TestExportEntries:
    """Verify export_entries method."""

    def test_export_dict_not_found(self):
        """Negative: non-existent dict_id raises ValueError."""
        session, engine = make_session()
        try:
            with pytest.raises(ValueError, match="Dictionary not found"):
                DictionaryImportExport.export_entries(session, "nonexistent")
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_export_empty(self):
        """Edge: export with no entries returns header-only CSV."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            result = DictionaryImportExport.export_entries(session, d.id)
            assert "source_term" in result
            lines = result.strip().split("\n")
            assert len(lines) == 1  # header only
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_export_with_entries(self):
        """Happy: export returns CSV with entries."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            e1 = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="hola",
                source_language="en", target_language="es",
            )
            e2 = DictionaryEntry(
                dictionary_id=d.id, source_term="world",
                source_term_normalized="world", target_term="mundo",
                source_language="en", target_language="es",
            )
            session.add_all([e1, e2])
            session.commit()

            result = DictionaryImportExport.export_entries(session, d.id)
            reader = csv.DictReader(io.StringIO(result))
            rows = list(reader)
            assert len(rows) == 2
            assert rows[0]["source_term"] == "hello"
            assert rows[0]["target_term"] == "hola"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_export_tsv_delimiter(self):
        """Happy: export with tab delimiter."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            e = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="hola",
                source_language="en", target_language="es",
            )
            session.add(e)
            session.commit()

            result = DictionaryImportExport.export_entries(session, d.id, delimiter="\t")
            assert "\t" in result
            lines = result.strip().split("\n")
            assert len(lines) == 2
            assert "\t" in lines[1]
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)


class TestMigrateOldEntries:
    """Verify migrate_old_entries method."""

    def test_no_old_entries(self):
        """Edge: no entries with 'und' language returns zeros."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            e = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="hola",
                source_language="en", target_language="es",
            )
            session.add(e)
            session.commit()

            result = DictionaryImportExport.migrate_old_entries(session)
            assert result["migrated_source"] == 0
            assert result["migrated_target"] == 0
            assert result["total_processed"] == 0
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_migrate_source_only(self):
        """Happy: migrates source_language from origin."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            e = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="hola",
                source_language="und", target_language="und",
                origin_source_language="en",
            )
            session.add(e)
            session.commit()

            result = DictionaryImportExport.migrate_old_entries(session)
            assert result["migrated_source"] == 1
            assert result["total_processed"] >= 1

            session.refresh(e)
            assert e.source_language == "en"
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)

    def test_skip_when_both_und_no_origin(self):
        """Edge: entry with both und but no origin is skipped."""
        session, engine = make_session()
        try:
            d = _make_dict(session)
            e = DictionaryEntry(
                dictionary_id=d.id, source_term="hello",
                source_term_normalized="hello", target_term="hola",
                source_language="und", target_language="und",
            )
            session.add(e)
            session.commit()

            result = DictionaryImportExport.migrate_old_entries(session)
            assert result["skipped"] >= 1
        finally:
            session.close()
            Base.metadata.drop_all(bind=engine)
# #endregion Test.DictionaryImportExport
