"""
Extra coverage tests for library_routes.py.

Targets the ~111 statements not exercised by test_library_routes_coverage.py
and test_library_routes_deep_coverage.py:

- get_authenticated_user_password: session-store hit, g.user_password fallback,
  AuthenticationRequiredError raise
- download_all_text SSE: auth failure path, resources needing processing,
  txt_path exists pre-scan, download success/failure/exception branches
- download_bulk SSE: auth failure, queue items present (pdf mode, text_only mode),
  exception with paywall/server/generic phrase, queue empty then re-queued
- queue_all_undownloaded: resource with no URL, existing queue entry reset to
  pending, existing queue entry already pending, no filter result for resource
- download_source: existing queue entry reset to pending
- get_research_sources: malformed URL falls back to empty domain
"""

from contextlib import contextmanager
from unittest.mock import MagicMock, Mock, patch

import pytest
from flask import Flask, jsonify

from local_deep_research.web.auth.routes import auth_bp
from local_deep_research.research_library.routes.library_routes import (
    library_bp,
)

# ---------------------------------------------------------------------------
# Constants (matching history_routes reference pattern)
# ---------------------------------------------------------------------------

MODULE = "local_deep_research.research_library.routes.library_routes"
AUTH_DB_MANAGER = "local_deep_research.web.auth.decorators.db_manager"

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


def _mock_auth():
    """Return a MagicMock that satisfies login_required db_manager check."""
    m = MagicMock()
    m.is_user_connected.return_value = True
    m.connections = {"testuser": True}
    m.has_encryption = False
    return m


def _make_db_ctx(mock_session):
    """Build a mock context-manager for get_user_db_session."""
    ctx = MagicMock()
    ctx.__enter__ = MagicMock(return_value=mock_session)
    ctx.__exit__ = MagicMock(return_value=None)
    return ctx


def _build_mock_query(
    all_result=None, first_result=None, count_result=0, get_result=None
):
    q = Mock()
    q.all.return_value = all_result if all_result is not None else []
    q.first.return_value = first_result
    q.count.return_value = count_result
    q.get.return_value = get_result
    q.filter_by.return_value = q
    q.filter.return_value = q
    q.order_by.return_value = q
    q.outerjoin.return_value = q
    q.join.return_value = q
    q.limit.return_value = q
    q.offset.return_value = q
    q.delete.return_value = 0
    q.is_.return_value = q
    return q


@contextmanager
def _auth_client(
    app,
    library_service=None,
    download_service=None,
    mock_db_session=None,
    settings_overrides=None,
    get_auth_password="mock_password",
    render_return="<html>ok</html>",
    extra_patches=None,
):
    """
    Authenticated test client with all external dependencies mocked.

    Yields (client, ctx_dict) where ctx_dict contains references to mocks.
    """
    mock_db = _mock_auth()
    lib_svc = library_service or Mock()
    lib_cls = Mock(return_value=lib_svc)
    dl_svc = download_service or Mock()
    dl_svc.__enter__ = Mock(return_value=dl_svc)
    dl_svc.__exit__ = Mock(return_value=False)
    dl_cls = Mock(return_value=dl_svc)
    db_session = mock_db_session or Mock()
    if not hasattr(db_session, "query") or not callable(
        getattr(db_session, "query", None)
    ):
        db_session = Mock()
        db_session.query = Mock(return_value=_build_mock_query())
    db_session.commit = Mock()
    db_session.add = Mock()

    @contextmanager
    def fake_get_user_db_session(*a, **kw):
        yield db_session

    mock_sm = Mock()
    defaults = {
        "research_library.pdf_storage_mode": "database",
        "research_library.shared_library": False,
        "research_library.storage_path": "/tmp/test_lib",
    }
    if settings_overrides:
        defaults.update(settings_overrides)
    mock_sm.get_setting.side_effect = lambda k, d=None: defaults.get(k, d)
    mock_render = Mock(return_value=render_return)

    patches = [
        patch(AUTH_DB_MANAGER, mock_db),
        patch(f"{MODULE}.LibraryService", lib_cls),
        patch(f"{MODULE}.DownloadService", dl_cls),
        patch(
            f"{MODULE}.get_user_db_session",
            side_effect=fake_get_user_db_session,
        ),
        patch(f"{MODULE}.get_settings_manager", return_value=mock_sm),
        patch(
            "local_deep_research.utilities.db_utils.get_settings_manager",
            return_value=mock_sm,
        ),
        patch(f"{MODULE}.render_template_with_defaults", mock_render),
        patch(
            f"{MODULE}.get_authenticated_user_password",
            return_value=get_auth_password,
        ),
    ]
    if extra_patches:
        patches.extend(extra_patches)

    started = []
    try:
        for p in patches:
            started.append(p.start())
        with app.test_client() as client:
            with client.session_transaction() as sess:
                sess["username"] = "testuser"
                sess["session_id"] = "test-session-id"
            yield (
                client,
                {
                    "library_service": lib_svc,
                    "download_service": dl_svc,
                    "download_cls": dl_cls,
                    "db_session": db_session,
                    "settings": mock_sm,
                    "render": mock_render,
                },
            )
    finally:
        for p in patches:
            p.stop()


def _authed_post(app, path, **kwargs):
    """Issue an authenticated POST request and return the response."""
    with app.test_client() as c:
        with c.session_transaction() as sess:
            sess["username"] = "testuser"
            sess["session_id"] = "test-session-id"
        return c.post(path, **kwargs)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def app():
    """Minimal Flask app with auth and library blueprints."""
    application = Flask(__name__)
    application.config["SECRET_KEY"] = "test-secret"
    application.config["TESTING"] = True
    application.register_blueprint(auth_bp)
    application.register_blueprint(library_bp)

    @application.errorhandler(500)
    def _handle_500(error):
        return jsonify({"error": "Internal server error"}), 500

    return application


# ---------------------------------------------------------------------------
# get_authenticated_user_password
# ---------------------------------------------------------------------------


# ---------------------------------------------------------------------------
# download_all_text SSE stream
# ---------------------------------------------------------------------------


class TestDownloadAllTextSseStream:
    """Exercise the SSE generator in download_all_text."""

    def test_auth_failure_yields_error_event(self, app):
        """When get_authenticated_user_password raises, SSE stream returns error."""
        from local_deep_research.web.exceptions import (
            AuthenticationRequiredError,
        )

        mock_db = _mock_auth()
        mock_sm = Mock()
        mock_sm.get_setting.return_value = "database"

        with (
            patch(AUTH_DB_MANAGER, mock_db),
            patch(f"{MODULE}.get_settings_manager", return_value=mock_sm),
            patch(
                "local_deep_research.utilities.db_utils.get_settings_manager",
                return_value=mock_sm,
            ),
            patch(f"{MODULE}.LibraryService", Mock()),
            patch(
                f"{MODULE}.get_authenticated_user_password",
                side_effect=AuthenticationRequiredError("need auth"),
            ),
        ):
            with app.test_client() as c:
                with c.session_transaction() as sess:
                    sess["username"] = "testuser"
                    sess["session_id"] = "test-session-id"
                resp = c.post("/library/api/download-all-text")
            assert resp.status_code == 200
            assert "text/event-stream" in resp.content_type
            data = resp.data.decode()
            assert "Authentication required" in data

    def test_processes_resources_needing_extraction(self, app):
        """Resources not in txt_path are downloaded; success path emits SSE."""
        resource = Mock()
        resource.id = 5
        resource.url = "https://arxiv.org/abs/1234"
        resource.title = "My Paper"

        db_session = Mock()
        db_session.query.return_value = _build_mock_query(all_result=[resource])

        dl_svc = Mock()
        dl_svc.download_as_text.return_value = (True, None)
        dl_svc.library_root = "/tmp/lib_no_txt"

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.DownloadService", return_value=dl_svc),
                patch(f"{MODULE}.is_downloadable_url", return_value=True),
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post("/library/api/download-all-text")
            assert resp.status_code == 200
            assert "text/event-stream" in resp.content_type
            body = resp.data.decode()
            assert "complete" in body

    def test_download_failure_emits_failed_status(self, app):
        """When download_as_text returns failure, status=failed is emitted."""
        resource = Mock()
        resource.id = 6
        resource.url = "https://arxiv.org/abs/5678"
        resource.title = "Failing Paper"

        db_session = Mock()
        db_session.query.return_value = _build_mock_query(all_result=[resource])

        dl_svc = Mock()
        dl_svc.download_as_text.return_value = (False, "Timeout error")
        dl_svc.library_root = "/tmp/lib_no_txt2"

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.DownloadService", return_value=dl_svc),
                patch(f"{MODULE}.is_downloadable_url", return_value=True),
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post("/library/api/download-all-text")
            body = resp.data.decode()
            assert "failed" in body

    def test_exception_during_download_emits_failed_status(self, app):
        """When download_as_text raises, status=failed is emitted."""
        resource = Mock()
        resource.id = 7
        resource.url = "https://arxiv.org/abs/9999"
        resource.title = "Exception Paper"

        db_session = Mock()
        db_session.query.return_value = _build_mock_query(all_result=[resource])

        dl_svc = Mock()
        dl_svc.download_as_text.side_effect = RuntimeError("crash")
        dl_svc.library_root = "/tmp/lib_no_txt3"

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.DownloadService", return_value=dl_svc),
                patch(f"{MODULE}.is_downloadable_url", return_value=True),
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post("/library/api/download-all-text")
            body = resp.data.decode()
            assert "failed" in body


# ---------------------------------------------------------------------------
# download_bulk SSE stream
# ---------------------------------------------------------------------------


class TestDownloadBulkSseStream:
    """Exercise the SSE generator in download_bulk with queue items present."""

    def test_auth_failure_in_bulk_yields_error_event(self, app):
        """When auth fails inside the generator, SSE error is emitted."""
        from local_deep_research.web.exceptions import (
            AuthenticationRequiredError,
        )

        mock_db = _mock_auth()
        mock_sm = Mock()
        mock_sm.get_setting.return_value = "database"

        with (
            patch(AUTH_DB_MANAGER, mock_db),
            patch(f"{MODULE}.get_settings_manager", return_value=mock_sm),
            patch(
                "local_deep_research.utilities.db_utils.get_settings_manager",
                return_value=mock_sm,
            ),
            patch(f"{MODULE}.LibraryService", Mock()),
            patch(
                f"{MODULE}.get_authenticated_user_password",
                side_effect=AuthenticationRequiredError("need auth"),
            ),
        ):
            with app.test_client() as c:
                with c.session_transaction() as sess:
                    sess["username"] = "testuser"
                    sess["session_id"] = "test-session-id"
                resp = c.post(
                    "/library/api/download-bulk",
                    json={"research_ids": ["r1"], "mode": "pdf"},
                    content_type="application/json",
                )
            assert resp.status_code == 200
            body = resp.data.decode()
            assert "Authentication required" in body

    def test_pdf_mode_processes_queue_items(self, app):
        """With queue items present, pdf mode calls download_resource per item."""
        queue_item = Mock()
        queue_item.resource_id = 10
        resource = Mock()
        resource.title = "Queue Paper"

        db_session = Mock()
        q = _build_mock_query(all_result=[queue_item], count_result=1)
        q.get.return_value = resource
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.download_resource.return_value = (True, None)
        dl_svc.queue_research_downloads.return_value = 0

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            assert resp.status_code == 200
            body = resp.data.decode()
            assert "complete" in body

    def test_text_only_mode_calls_download_as_text(self, app):
        """text_only mode calls download_as_text rather than download_resource."""
        queue_item = Mock()
        queue_item.resource_id = 11
        resource = Mock()
        resource.title = "Text Paper"

        db_session = Mock()
        q = _build_mock_query(all_result=[queue_item], count_result=1)
        q.get.return_value = resource
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.download_as_text.return_value = (True, None)
        dl_svc.download_resource.return_value = (True, None)
        dl_svc.queue_research_downloads.return_value = 0

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "text_only"},
                content_type="application/json",
            )
            assert resp.status_code == 200
            body = resp.data.decode()
            assert "complete" in body

    def test_exception_with_paywall_phrase_emits_skipped(self, app):
        """Exception containing 'paywall' results in status=skipped."""
        queue_item = Mock()
        queue_item.resource_id = 12
        resource = Mock()
        resource.title = "Paywalled Paper"

        db_session = Mock()
        q = _build_mock_query(all_result=[queue_item], count_result=1)
        q.get.return_value = resource
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.download_resource.side_effect = Exception(
            "paywall detected - cannot access"
        )
        dl_svc.queue_research_downloads.return_value = 0

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            body = resp.data.decode()
            assert "skipped" in body

    def test_exception_with_server_phrase_emits_failed(self, app):
        """Exception containing 'server' results in status=failed."""
        queue_item = Mock()
        queue_item.resource_id = 13
        resource = Mock()
        resource.title = "Server Error Paper"

        db_session = Mock()
        q = _build_mock_query(all_result=[queue_item], count_result=1)
        q.get.return_value = resource
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.download_resource.side_effect = Exception("server returned 503")
        dl_svc.queue_research_downloads.return_value = 0

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            body = resp.data.decode()
            assert "failed" in body

    def test_exception_with_generic_phrase_emits_failed(self, app):
        """Generic exception (no known phrase) results in status=failed."""
        queue_item = Mock()
        queue_item.resource_id = 14
        resource = Mock()
        resource.title = "Generic Error Paper"

        db_session = Mock()
        q = _build_mock_query(all_result=[queue_item], count_result=1)
        q.get.return_value = resource
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.download_resource.side_effect = Exception("something went wrong")
        dl_svc.queue_research_downloads.return_value = 0

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            body = resp.data.decode()
            assert "failed" in body

    def test_empty_queue_auto_queues_then_processes(self, app):
        """When no queue items found initially, queue_research_downloads is called."""
        db_session = Mock()
        empty_q = _build_mock_query(all_result=[], count_result=0)
        db_session.query = Mock(return_value=empty_q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.queue_research_downloads.return_value = 0
        dl_svc.download_resource.return_value = (True, None)

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            assert resp.status_code == 200
            body = resp.data.decode()
            assert "complete" in body
            dl_svc.queue_research_downloads.assert_called_once_with("r1", None)

    def test_queue_research_exception_continues(self, app):
        """Exception in queue_research_downloads is swallowed; processing continues."""
        db_session = Mock()
        empty_q = _build_mock_query(all_result=[], count_result=0)
        db_session.query = Mock(return_value=empty_q)
        db_session.commit = Mock()

        dl_svc = Mock()
        dl_svc.queue_research_downloads.side_effect = RuntimeError("db locked")
        dl_svc.download_resource.return_value = (True, None)

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(
                    "local_deep_research.utilities.resource_utils.safe_close",
                    return_value=None,
                ),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-bulk",
                json={"research_ids": ["r1"], "mode": "pdf"},
                content_type="application/json",
            )
            assert resp.status_code == 200
            body = resp.data.decode()
            assert "complete" in body


# ---------------------------------------------------------------------------
# queue_all_undownloaded edge cases
# ---------------------------------------------------------------------------


class TestQueueAllUndownloadedEdgeCases:
    """Exercise the branches inside queue_all_undownloaded not yet covered."""

    def test_resource_with_no_url_is_skipped(self, app):
        """Resources where url is None/empty are counted as skipped."""
        resource = Mock()
        resource.id = 20
        resource.url = None  # no URL
        resource.research_id = "r1"

        filter_result = Mock()
        filter_result.resource_id = 20
        filter_result.can_retry = True

        filter_summary = Mock()
        filter_summary.to_dict.return_value = {"total": 1}
        filter_summary.permanently_failed_count = 0
        filter_summary.temporarily_failed_count = 0

        db_session = Mock()
        q = _build_mock_query(all_result=[resource])
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()
        db_session.add = Mock()

        mock_rf = Mock()
        mock_rf.filter_downloadable_resources.return_value = [filter_result]
        mock_rf.get_filter_summary.return_value = filter_summary
        mock_rf.get_skipped_resources_info.return_value = []

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.ResourceFilter", return_value=mock_rf),
            ],
        ) as (client, _):
            resp = client.post("/library/api/queue-all-undownloaded")
            assert resp.status_code == 200
            data = resp.get_json()
            assert data["skipped"] >= 1
            assert data["queued"] == 0

    def test_existing_queue_entry_not_pending_is_reset(self, app):
        """When a queue entry exists but is not PENDING, it is reset to PENDING."""
        resource = Mock()
        resource.id = 21
        resource.url = "https://arxiv.org/abs/2222"
        resource.research_id = "r1"

        from local_deep_research.database.models.library import DocumentStatus

        existing_entry = Mock()
        existing_entry.status = "failed"
        existing_entry.completed_at = "2024-01-01"

        filter_result = Mock()
        filter_result.resource_id = 21
        filter_result.can_retry = True

        filter_summary = Mock()
        filter_summary.to_dict.return_value = {"total": 1}
        filter_summary.permanently_failed_count = 0
        filter_summary.temporarily_failed_count = 0

        db_session = Mock()

        # main_q returned for the outerjoin query (all_result=[resource])
        # queue_q returned for the filter_by(resource_id=...) query
        main_q = _build_mock_query(all_result=[resource])
        queue_q = _build_mock_query(first_result=existing_entry)
        main_q.filter_by = Mock(return_value=queue_q)

        db_session.query = Mock(return_value=main_q)
        db_session.commit = Mock()
        db_session.add = Mock()

        mock_rf = Mock()
        mock_rf.filter_downloadable_resources.return_value = [filter_result]
        mock_rf.get_filter_summary.return_value = filter_summary
        mock_rf.get_skipped_resources_info.return_value = []

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.ResourceFilter", return_value=mock_rf),
                patch(f"{MODULE}.is_downloadable_domain", return_value=True),
            ],
        ) as (client, _):
            resp = client.post("/library/api/queue-all-undownloaded")
            assert resp.status_code == 200
            data = resp.get_json()
            assert data["queued"] >= 1
            # Entry should have been reset to PENDING
            assert existing_entry.status == DocumentStatus.PENDING
            assert existing_entry.completed_at is None

    def test_existing_queue_entry_already_pending_counted(self, app):
        """When a queue entry is already PENDING, it is still counted."""
        resource = Mock()
        resource.id = 22
        resource.url = "https://arxiv.org/abs/3333"
        resource.research_id = "r1"

        from local_deep_research.database.models.library import DocumentStatus

        existing_entry = Mock()
        existing_entry.status = DocumentStatus.PENDING

        filter_result = Mock()
        filter_result.resource_id = 22
        filter_result.can_retry = True

        filter_summary = Mock()
        filter_summary.to_dict.return_value = {"total": 1}
        filter_summary.permanently_failed_count = 0
        filter_summary.temporarily_failed_count = 0

        db_session = Mock()
        main_q = _build_mock_query(all_result=[resource])
        queue_q = _build_mock_query(first_result=existing_entry)
        main_q.filter_by = Mock(return_value=queue_q)
        db_session.query = Mock(return_value=main_q)
        db_session.commit = Mock()
        db_session.add = Mock()

        mock_rf = Mock()
        mock_rf.filter_downloadable_resources.return_value = [filter_result]
        mock_rf.get_filter_summary.return_value = filter_summary
        mock_rf.get_skipped_resources_info.return_value = []

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.ResourceFilter", return_value=mock_rf),
                patch(f"{MODULE}.is_downloadable_domain", return_value=True),
            ],
        ) as (client, _):
            resp = client.post("/library/api/queue-all-undownloaded")
            assert resp.status_code == 200
            data = resp.get_json()
            assert data["queued"] >= 1

    def test_no_filter_result_for_resource_skips(self, app):
        """Resources with no filter result at all are counted as skipped."""
        resource = Mock()
        resource.id = 24
        resource.url = "https://arxiv.org/abs/5555"
        resource.research_id = "r1"

        filter_summary = Mock()
        filter_summary.to_dict.return_value = {"total": 1}
        filter_summary.permanently_failed_count = 0
        filter_summary.temporarily_failed_count = 0

        db_session = Mock()
        q = _build_mock_query(all_result=[resource])
        db_session.query = Mock(return_value=q)
        db_session.commit = Mock()

        mock_rf = Mock()
        # Returns empty list — no filter result for resource id 24
        mock_rf.filter_downloadable_resources.return_value = []
        mock_rf.get_filter_summary.return_value = filter_summary
        mock_rf.get_skipped_resources_info.return_value = []

        with _auth_client(
            app,
            mock_db_session=db_session,
            extra_patches=[
                patch(f"{MODULE}.ResourceFilter", return_value=mock_rf),
            ],
        ) as (client, _):
            resp = client.post("/library/api/queue-all-undownloaded")
            assert resp.status_code == 200
            data = resp.get_json()
            assert data["queued"] == 0
            assert data["skipped"] >= 1


# ---------------------------------------------------------------------------
# download_source: existing queue entry reset to pending
# ---------------------------------------------------------------------------


class TestDownloadSourceExistingQueueEntry:
    """Covers the branch where a queue entry already exists and is reset."""

    def test_existing_queue_entry_is_reset_to_pending(self, app):
        """When an existing queue entry is found, its status/priority are updated."""
        resource = Mock()
        resource.id = 30
        resource.research_id = "r1"

        from local_deep_research.database.models.library import DocumentStatus

        existing_entry = Mock()
        existing_entry.status = "failed"
        existing_entry.priority = 0

        db_session = Mock()

        # query().filter_by() for the resource lookup returns resource
        # query().filter_by() for the queue lookup returns existing_entry
        resource_q = _build_mock_query(first_result=resource)
        queue_q = _build_mock_query(first_result=existing_entry)

        call_n = [0]

        def mock_filter_by(**kw):
            call_n[0] += 1
            if call_n[0] <= 1:
                return resource_q
            return queue_q

        resource_q.filter_by = Mock(side_effect=mock_filter_by)
        queue_q.filter_by = Mock(return_value=queue_q)
        db_session.query = Mock(return_value=resource_q)
        db_session.commit = Mock()
        db_session.add = Mock()

        dl_svc = Mock()
        dl_svc.download_resource.return_value = (True, None)
        dl_svc.__enter__ = Mock(return_value=dl_svc)
        dl_svc.__exit__ = Mock(return_value=False)

        with _auth_client(
            app,
            mock_db_session=db_session,
            download_service=dl_svc,
            extra_patches=[
                patch(f"{MODULE}.is_downloadable_domain", return_value=True),
                patch(f"{MODULE}.get_document_for_resource", return_value=None),
            ],
        ) as (client, _):
            resp = client.post(
                "/library/api/download-source",
                json={
                    "research_id": "r1",
                    "url": "https://arxiv.org/abs/1234",
                },
                content_type="application/json",
            )
            assert resp.status_code == 200
            # Entry should have been reset
            assert existing_entry.status == DocumentStatus.PENDING
            assert existing_entry.priority == 1
