"""Extra coverage tests for research_sources_service.py — untested branches."""

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

MODULE = "local_deep_research.web.services.research_sources_service"


def _fake_session_ctx(session=None):
    if session is None:
        session = MagicMock()

    @contextmanager
    def ctx(username=None):
        yield session

    return ctx


def _make_resource(
    id=1,
    url="http://example.com",
    title="Title",
    content_preview="Preview",
    source_type="web",
    resource_metadata=None,
    created_at="2025-01-01T00:00:00",
):
    r = MagicMock()
    r.id = id
    r.url = url
    r.title = title
    r.content_preview = content_preview
    r.source_type = source_type
    r.resource_metadata = resource_metadata
    r.created_at = created_at
    return r


class TestSaveResearchSources:
    def test_empty_sources_returns_zero(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        assert ResearchSourcesService.save_research_sources("r1", []) == 0
        assert ResearchSourcesService.save_research_sources("r1", None) == 0

    def test_skips_existing_resources(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.count.return_value = 5

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.save_research_sources(
                "r1", [{"url": "http://a.com"}], "user"
            )

        assert result == 5

    def test_saves_sources_successfully(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.count.return_value = 0

        sources = [
            {"url": "http://a.com", "title": "A", "snippet": "text"},
            {"link": "http://b.com", "name": "B", "description": "desc"},
        ]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.save_research_sources(
                "r1", sources, "user"
            )

        assert result == 2
        assert ms.add.call_count == 2
        ms.commit.assert_called_once()

    def test_skips_source_without_url(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.count.return_value = 0

        sources = [{"title": "no url"}, {"url": "http://a.com"}]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.save_research_sources(
                "r1", sources, "user"
            )

        assert result == 1

    def test_per_source_exception_continues(self):
        """One bad source doesn't stop others from being saved."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.count.return_value = 0

        call_count = 0

        def add_side_effect(obj):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                raise RuntimeError("bad source")

        ms.add.side_effect = add_side_effect

        sources = [
            {"url": "http://bad.com"},
            {"url": "http://good.com"},
        ]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.save_research_sources(
                "r1", sources, "user"
            )

        # First failed, second should still be counted
        # (but since add raises, saved_count doesn't increment for first)
        assert result >= 0

    def test_db_exception_propagates(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        import pytest

        with (
            patch(
                f"{MODULE}.get_user_db_session",
                side_effect=RuntimeError("db fail"),
            ),
            pytest.raises(RuntimeError),
        ):
            ResearchSourcesService.save_research_sources(
                "r1", [{"url": "http://a.com"}], "user"
            )


class TestGetResearchSources:
    def test_returns_sources(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        resource = _make_resource(resource_metadata={"key": "val"})
        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = [
            resource
        ]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.get_research_sources("r1", "user")

        assert len(result) == 1
        assert result[0]["url"] == "http://example.com"
        assert result[0]["metadata"] == {"key": "val"}

    def test_null_metadata_returns_empty_dict(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        resource = _make_resource(resource_metadata=None)
        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = [
            resource
        ]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.get_research_sources("r1", "user")

        assert result[0]["metadata"] == {}

    def test_empty_results(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = []

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.get_research_sources("r1", "user")

        assert result == []

    def test_db_exception_propagates(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        import pytest

        with (
            patch(
                f"{MODULE}.get_user_db_session",
                side_effect=RuntimeError("db fail"),
            ),
            pytest.raises(RuntimeError),
        ):
            ResearchSourcesService.get_research_sources("r1", "user")


class TestUpdateResearchWithSources:
    def test_updates_metadata(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        research = MagicMock()
        research.research_meta = {}
        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.first.return_value = (
            research
        )
        ms.query.return_value.filter_by.return_value.count.return_value = 0

        sources = [{"url": "http://a.com"}]

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.update_research_with_sources(
                "r1", sources, "user"
            )

        assert result is True

    def test_research_not_found(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        ms = MagicMock()
        # save_research_sources query
        ms.query.return_value.filter_by.return_value.count.return_value = 0
        # update query
        ms.query.return_value.filter_by.return_value.first.return_value = None

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.update_research_with_sources(
                "r1", [{"url": "http://a.com"}], "user"
            )

        assert result is False

    def test_exception_returns_false(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        with patch(
            f"{MODULE}.get_user_db_session",
            side_effect=RuntimeError("fail"),
        ):
            result = ResearchSourcesService.update_research_with_sources(
                "r1", [{"url": "http://a.com"}], "user"
            )

        assert result is False

    def test_null_metadata_initialized(self):
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        research = MagicMock()
        research.research_meta = None
        ms = MagicMock()
        ms.query.return_value.filter_by.return_value.first.return_value = (
            research
        )
        ms.query.return_value.filter_by.return_value.count.return_value = 0

        with patch(f"{MODULE}.get_user_db_session", _fake_session_ctx(ms)):
            result = ResearchSourcesService.update_research_with_sources(
                "r1", [], "user"
            )

        assert result is True
