"""Tests for benchmark service persistence of ldr_version + settings_snapshot.

Pins the contract added by the migration-0014 PR:
- start_benchmark writes ldr_version (== current __version__) on the row.
- start_benchmark writes a redacted settings_snapshot.
- A LookupError raised by SettingsManager.get_all_settings (as happens with
  legacy CHAT-typed enum rows) is swallowed; benchmark proceeds with empty
  snapshot and ldr_version still recorded.

Patterned after test_benchmark_service.test_start_benchmark_creates_thread.
The thread is stubbed so we only exercise the synchronous prelude where the
new fields are written.
"""

from unittest.mock import Mock, patch


def _make_run(**overrides):
    mock_run = Mock()
    mock_run.id = 1
    mock_run.config_hash = "abc12345"
    mock_run.datasets_config = {"simpleqa": {"count": 1}}
    mock_run.search_config = {}
    mock_run.evaluation_config = {}
    # Start with no provenance set; assertions read the post-call value.
    mock_run.ldr_version = None
    mock_run.settings_snapshot = None
    for k, v in overrides.items():
        setattr(mock_run, k, v)
    return mock_run


def _patched_start_benchmark(service, mock_run, snapshot):
    """Run service.start_benchmark with all collaborators stubbed."""
    with patch(
        "local_deep_research.database.session_context.get_user_db_session"
    ) as mock_get_session:
        mock_session = Mock()
        mock_session.__enter__ = Mock(return_value=mock_session)
        mock_session.__exit__ = Mock(return_value=False)
        mock_get_session.return_value = mock_session
        mock_session.query.return_value.filter.return_value.first.return_value = mock_run

        with patch(
            "local_deep_research.settings.SettingsManager"
        ) as mock_settings_mgr:
            if isinstance(snapshot, Exception):
                mock_settings_mgr.return_value.get_all_settings.side_effect = (
                    snapshot
                )
            else:
                mock_settings_mgr.return_value.get_all_settings.return_value = (
                    snapshot
                )

            with patch("flask.session", {"session_id": "s"}):
                with patch(
                    "local_deep_research.database.session_passwords.session_password_store"
                ):
                    with patch.object(
                        service, "_run_benchmark_thread", return_value=None
                    ):
                        return service.start_benchmark(
                            mock_run.id, username="testuser"
                        )


def test_start_benchmark_persists_ldr_version():
    from local_deep_research import __version__
    from local_deep_research.benchmarks.web_api.benchmark_service import (
        BenchmarkService,
    )

    service = BenchmarkService(socket_service=Mock())
    mock_run = _make_run()
    result = _patched_start_benchmark(service, mock_run, snapshot={})
    assert result is True
    assert mock_run.ldr_version == __version__


def test_start_benchmark_persists_redacted_snapshot():
    """The persisted snapshot must redact secrets via DataSanitizer.redact_settings_snapshot."""
    from local_deep_research.benchmarks.web_api.benchmark_service import (
        BenchmarkService,
    )

    service = BenchmarkService(socket_service=Mock())
    mock_run = _make_run()
    snapshot = {
        "llm.openai.api_key": {
            "value": "sk-real-secret",
            "ui_element": "password",
            "type": "LLM",
        },
        "search.fetch.mode": {
            "value": "summary_focus_query",
            "ui_element": "select",
            "type": "SEARCH",
        },
    }
    _patched_start_benchmark(service, mock_run, snapshot=snapshot)

    persisted = mock_run.settings_snapshot
    assert isinstance(persisted, dict)
    # Secret value redacted, metadata intact.
    assert persisted["llm.openai.api_key"]["value"] == "[REDACTED]"
    assert persisted["llm.openai.api_key"]["ui_element"] == "password"
    # Non-secret values pass through.
    assert persisted["search.fetch.mode"]["value"] == "summary_focus_query"


def test_start_benchmark_does_not_mutate_in_memory_snapshot():
    """The unredacted snapshot the background thread uses to authenticate
    against providers must NOT be mutated by the redaction step."""
    from local_deep_research.benchmarks.web_api.benchmark_service import (
        BenchmarkService,
    )

    service = BenchmarkService(socket_service=Mock())
    mock_run = _make_run()
    snapshot = {
        "llm.openai.api_key": {
            "value": "sk-real-secret",
            "ui_element": "password",
        }
    }
    _patched_start_benchmark(service, mock_run, snapshot=snapshot)
    # Original dict still has the real secret.
    assert snapshot["llm.openai.api_key"]["value"] == "sk-real-secret"
    # The active_runs in-memory copy keeps the unredacted snapshot for the
    # background thread (which needs the real keys to call providers).
    assert (
        service.active_runs[1]["data"]["settings_snapshot"][
            "llm.openai.api_key"
        ]["value"]
        == "sk-real-secret"
    )


def test_start_benchmark_survives_settings_lookup_error():
    """A LookupError from get_all_settings (e.g. stale CHAT enum row) must
    not crash benchmark start. Snapshot becomes empty; ldr_version is still
    recorded so users can tell which version produced the empty snapshot."""
    from local_deep_research import __version__
    from local_deep_research.benchmarks.web_api.benchmark_service import (
        BenchmarkService,
    )

    service = BenchmarkService(socket_service=Mock())
    mock_run = _make_run()
    err = LookupError("'CHAT' is not among the defined enum values")
    result = _patched_start_benchmark(service, mock_run, snapshot=err)

    assert result is True
    assert mock_run.ldr_version == __version__
    assert mock_run.settings_snapshot == {}
