# #region TestTranslateExecutorFilter [C:3] [TYPE Module] [SEMANTICS test, translate, executor, filter, new-key-only]
# @BRIEF Verify TranslationExecutor._filter_new_keys contracts — key dedup, empty-set, guard triggers.
# @RELATION BINDS_TO -> [TranslationExecutor]
# @TEST_EDGE: no_prior_run — all rows pass through when no previous run exists
# @TEST_EDGE: filter_existing_keys — rows with keys from prior run are skipped
# @TEST_EDGE: composite_keys — multiple target_key_cols produce correct dedup
# @TEST_EDGE: all_existing — zero new rows returns empty list
# @TEST_EDGE: empty_key_cols — guard triggers, all rows returned with warning
# @TEST_EDGE: null_source_data — None source_data is safely handled
# @TEST_INVARIANT: new_key_only_filter -> VERIFIED_BY: [test_no_prior_run_returns_all_rows, test_filters_existing_keys, test_composite_keys]

from pathlib import Path
import sys

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

from unittest.mock import MagicMock

from src.plugins.translate.executor import TranslationExecutor

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_executor(mock_db=None):
    """Create a TranslationExecutor with a mocked DB session and config."""
    db = mock_db or MagicMock()
    cfg = MagicMock()
    return TranslationExecutor(db, cfg, current_user="test_user")


def _make_job(source_key_cols=None, target_key_cols=None):
    """Build a MagicMock TranslationJob with key columns configured."""
    job = MagicMock()
    job.id = "job-test-1"
    job.source_key_cols = source_key_cols or []
    job.target_key_cols = target_key_cols or []
    return job


def _make_rows(*source_data_dicts):
    """Build source_rows list from source_data dicts."""
    rows = []
    for sd in source_data_dicts:
        rows.append({
            "row_index": sd.get("_idx", "0"),
            "source_text": sd.get("text", ""),
            "source_data": sd,
        })
    return rows


def _make_prev_records(run_id, source_data_dicts):
    """Build MagicMock TranslationRecord list with source_data and SUCCESS status."""
    recs = []
    for sd in source_data_dicts:
        rec = MagicMock()
        rec.run_id = run_id
        rec.status = "SUCCESS"
        rec.source_data = sd
        recs.append(rec)
    return recs


def _wire_query_chain(mock_db, prev_run, prev_records):
    """Wire mock DB for _filter_new_keys: two sequential db.query() calls.

    Query 1: db.query(TranslationRun).filter(a,b,c,d).order_by(desc).first() → prev_run
    Query 2: db.query(TranslationRecord).filter(a,b).all() → prev_records
    """
    q1 = MagicMock()
    q1.filter.return_value.order_by.return_value.first.return_value = prev_run
    q2 = MagicMock()
    q2.filter.return_value.all.return_value = prev_records
    mock_db.query.side_effect = [q1, q2]


# #region test_no_prior_run_returns_all_rows [C:1] [TYPE Function]
def test_no_prior_run_returns_all_rows():
    """When no prior TranslationRun exists, all source_rows pass through unchanged."""
    executor = _make_executor()
    job = _make_job(source_key_cols=["id"], target_key_cols=["id"])

    rows = _make_rows(
        {"id": 1, "text": "hello", "_idx": "0"},
        {"id": 2, "text": "world", "_idx": "1"},
        {"id": 3, "text": "test",  "_idx": "2"},
    )

    # Mock: no prior run (query returns None)
    _wire_query_chain(executor.db, prev_run=None, prev_records=[])

    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 3
    assert result == rows  # identity — all pass through
# #endregion test_no_prior_run_returns_all_rows


# #region test_filters_existing_keys [C:1] [TYPE Function]
def test_filters_existing_keys():
    """Rows whose key exists in a prior run's records are excluded."""
    executor = _make_executor()
    job = _make_job(source_key_cols=["id"], target_key_cols=["id"])

    rows = _make_rows(
        {"id": 1, "text": "existing", "_idx": "0"},
        {"id": 2, "text": "new",      "_idx": "1"},
        {"id": 3, "text": "existing", "_idx": "2"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    # Only id=1 and id=3 were previously translated successfully
    prev_recs = _make_prev_records("prev-run-1", [
        {"id": 1, "text": "existing"},
        {"id": 3, "text": "existing"},
    ])

    _wire_query_chain(executor.db, mock_prev_run, prev_recs)

    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 1, f"Expected 1 new row, got {len(result)}"
    assert result[0]["source_data"]["id"] == 2
# #endregion test_filters_existing_keys


# #region test_composite_keys [C:1] [TYPE Function]
def test_composite_keys():
    """Multiple target_key_cols produce correct composite key dedup."""
    executor = _make_executor()
    job = _make_job(target_key_cols=["schema", "table"])

    rows = _make_rows(
        {"schema": "public", "table": "users",  "text": "a", "_idx": "0"},
        {"schema": "public", "table": "orders", "text": "b", "_idx": "1"},
        {"schema": "admin",  "table": "users",  "text": "c", "_idx": "2"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    # Only (public, users) was already translated
    prev_recs = _make_prev_records("prev-run-1", [
        {"schema": "public", "table": "users"},
    ])

    _wire_query_chain(executor.db, mock_prev_run, prev_recs)

    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 2, f"Expected 2 new rows, got {len(result)}"

    result_keys = {
        tuple(str(r["source_data"].get(k, "")) for k in ["schema", "table"])
        for r in result
    }
    assert ("public", "orders") in result_keys
    assert ("admin", "users") in result_keys
    assert ("public", "users") not in result_keys
# #endregion test_composite_keys


# #region test_all_keys_existing_returns_empty [C:1] [TYPE Function]
def test_all_keys_existing_returns_empty():
    """When every source row's key already exists, the filtered list is empty."""
    executor = _make_executor()
    job = _make_job(source_key_cols=["id"])

    rows = _make_rows(
        {"id": 1, "text": "a", "_idx": "0"},
        {"id": 2, "text": "b", "_idx": "1"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    prev_recs = _make_prev_records("prev-run-1", [
        {"id": 1}, {"id": 2},
    ])

    _wire_query_chain(executor.db, mock_prev_run, prev_recs)

    result = executor._filter_new_keys(job, "run-1", rows)
    assert result == [], "All keys existing should produce empty result"
# #endregion test_all_keys_existing_returns_empty


# #region test_empty_key_cols_returns_all_rows [C:1] [TYPE Function]
def test_empty_key_cols_returns_all_rows():
    """When no key columns are configured, guard triggers — all rows returned."""
    executor = _make_executor()
    job = _make_job(source_key_cols=[], target_key_cols=[])

    rows = _make_rows(
        {"id": 1, "text": "a", "_idx": "0"},
        {"id": 2, "text": "b", "_idx": "1"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    _wire_query_chain(executor.db, mock_prev_run, prev_records=[])

    # Even with a prior run and records, no key cols means all rows pass through
    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 2
    assert result == rows
# #endregion test_empty_key_cols_returns_all_rows


# #region test_null_source_data_handled [C:1] [TYPE Function]
def test_null_source_data_handled():
    """None source_data is safely handled with or {} fallback — no crash."""
    executor = _make_executor()
    job = _make_job(source_key_cols=["id"])

    rows = [
        {"row_index": "0", "source_text": "a", "source_data": None},
        {"row_index": "1", "source_text": "b", "source_data": {"id": 2}},
    ]

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    # Previous run only had id=2
    prev_recs = _make_prev_records("prev-run-1", [{"id": 2}])

    _wire_query_chain(executor.db, mock_prev_run, prev_recs)

    # Should NOT crash on None source_data
    result = executor._filter_new_keys(job, "run-1", rows)

    # The row with None source_data has key_tuple ("",) — NOT in existing_keys
    # (existing has ("2",)). So it should pass through.
    assert len(result) == 1, (
        f"Expected 1 new row (None source_data treated as ''), got {len(result)}"
    )
    assert result[0]["source_data"] is None
# #endregion test_null_source_data_handled


# #region test_prior_run_no_records_returns_all [C:1] [TYPE Function]
def test_prior_run_no_records_returns_all():
    """Prior run exists but has no successful records — all rows treated as new."""
    executor = _make_executor()
    job = _make_job(source_key_cols=["id"])

    rows = _make_rows(
        {"id": 1, "text": "a", "_idx": "0"},
        {"id": 2, "text": "b", "_idx": "1"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    _wire_query_chain(executor.db, mock_prev_run, prev_records=[])

    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 2
    assert result == rows
# #endregion test_prior_run_no_records_returns_all


# #region test_key_cols_none_returns_all [C:1] [TYPE Function]
def test_key_cols_none_returns_all():
    """When source_key_cols and target_key_cols are both None, all rows pass through."""
    executor = _make_executor()
    # Job with no key_cols at all (None, not empty list)
    job = MagicMock()
    job.id = "job-test-1"
    job.source_key_cols = None
    job.target_key_cols = None

    rows = _make_rows(
        {"id": 1, "text": "a", "_idx": "0"},
    )

    mock_prev_run = MagicMock()
    mock_prev_run.id = "prev-run-1"

    _wire_query_chain(executor.db, mock_prev_run, prev_records=[])

    result = executor._filter_new_keys(job, "run-1", rows)
    assert len(result) == 1
    assert result == rows
# #endregion test_key_cols_none_returns_all


# #endregion TestTranslateExecutorFilter
