"""Exception text must not leak into the downloader's return message.

CodeQL alerts 7650 and 7684 flagged `str(exc)` flowing from the
build_db failure path through `download_journal_data` into the
HTTP response. The fix sanitizes at the source — only the exception
*class name* survives. The log line (server-side only) still carries
the full traceback.
"""

from unittest.mock import patch

import pytest

from local_deep_research.journal_quality.downloader import (
    JOURNAL_DATA_VERSION,
    download_journal_data,
)


@pytest.fixture
def tmp_data_dir(tmp_path):
    with patch(
        "local_deep_research.journal_quality.downloader._get_data_dir",
        return_value=tmp_path,
    ):
        yield tmp_path


def _fake_disk_usage(free_bytes):
    class _Usage:
        total = 10 * 1024**3
        used = total - free_bytes
        free = free_bytes

    return _Usage()


SENSITIVE_SUBSTRINGS = [
    "/home/",
    "Traceback",
    'File "',
    "line ",
    "sqlalchemy.",
    "SELECT ",
    "INSERT ",
    "near SYNTAX",
]


def test_build_db_exception_text_does_not_leak_into_message(tmp_data_dir):
    """Ensure str(exc) from build_db failure never reaches the caller."""

    # Craft an exception whose str() would leak system information.
    leaking_exc = RuntimeError(
        "SELECT * FROM journals WHERE h_index > 5 "
        '-- File "/home/user/.ldr/secret.db" line 42'
    )

    with (
        patch(
            "shutil.disk_usage",
            return_value=_fake_disk_usage(10 * 1024**3),
        ),
        patch(
            "local_deep_research.journal_quality.downloader._fetch_openalex_sources",
            return_value=1,
        ),
        patch(
            "local_deep_research.journal_quality.downloader._fetch_doaj_journals",
            return_value=1,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.institutions."
            "InstitutionSource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.jabref."
            "JabRefSource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.predatory."
            "PredatorySource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.db.build_db",
            side_effect=leaking_exc,
        ),
    ):
        success, message = download_journal_data(force=True)

    assert success is False
    # The class name is fine — callers can show "RuntimeError" safely.
    assert "RuntimeError" in message
    # None of the sensitive content must appear.
    for needle in SENSITIVE_SUBSTRINGS:
        assert needle not in message, (
            f"Sanitization regression: {needle!r} leaked into {message!r}"
        )


def test_healthy_success_message_has_no_exception_artifacts(tmp_data_dir):
    """On the happy path, success message is count+elapsed only."""

    with (
        patch(
            "shutil.disk_usage",
            return_value=_fake_disk_usage(10 * 1024**3),
        ),
        patch(
            "local_deep_research.journal_quality.downloader._fetch_openalex_sources",
            return_value=42,
        ),
        patch(
            "local_deep_research.journal_quality.downloader._fetch_doaj_journals",
            return_value=7,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.institutions."
            "InstitutionSource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.jabref."
            "JabRefSource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.data_sources.predatory."
            "PredatorySource.fetch",
            return_value=0,
        ),
        patch(
            "local_deep_research.journal_quality.db.build_db",
            return_value=None,
        ),
    ):
        success, message = download_journal_data(force=True)

    assert success is True
    assert "42" in message  # openalex count
    assert JOURNAL_DATA_VERSION  # sanity
    for needle in SENSITIVE_SUBSTRINGS:
        assert needle not in message
