"""
Tests for web/services/research_sources_service.py

Tests cover:
- ResearchSourcesService.save_research_sources()
- ResearchSourcesService.get_research_sources()
- ResearchSourcesService.update_research_with_sources()
"""

from unittest.mock import Mock, patch, MagicMock


class TestSaveResearchSources:
    """Tests for save_research_sources method."""

    def test_save_research_sources_empty_list(self):
        """Test saving empty list returns 0."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        result = ResearchSourcesService.save_research_sources(
            "test-id", [], username="testuser"
        )

        assert result == 0

    def test_save_research_sources_success(self):
        """Test successful source saving."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [
            {
                "url": "https://example.com/1",
                "title": "Source 1",
                "snippet": "Test",
            },
            {"url": "https://example.com/2", "title": "Source 2"},
        ]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.count.return_value = 0
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

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

    def test_save_research_sources_skips_existing(self):
        """Test skipping save when sources already exist."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [{"url": "https://example.com", "title": "Test"}]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            # Return that 5 resources already exist
            mock_session.query.return_value.filter_by.return_value.count.return_value = 5
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

            assert result == 5
            mock_session.add.assert_not_called()

    def test_save_research_sources_skips_no_url(self):
        """Test skipping sources without URL."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [
            {"title": "No URL source"},  # No url field
            {"url": "", "title": "Empty URL"},  # Empty url
            {"url": "https://valid.com", "title": "Valid"},
        ]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.count.return_value = 0
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

            # Only valid URL should be saved
            assert result == 1

    def test_save_research_sources_uses_link_fallback(self):
        """Test using 'link' as fallback for 'url'."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [{"link": "https://example.com", "title": "Test"}]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.count.return_value = 0
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

            assert result == 1

    def test_save_research_sources_truncates_snippet(self):
        """Test that long snippets are truncated to 1000 chars."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        long_snippet = "x" * 2000
        sources = [
            {
                "url": "https://example.com",
                "title": "Test",
                "snippet": long_snippet,
            }
        ]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.count.return_value = 0
            mock_get_session.return_value = mock_session

            ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

            # Verify the resource was added with truncated preview
            add_call = mock_session.add.call_args
            resource = add_call[0][0]
            assert len(resource.content_preview) == 1000

    def test_save_research_sources_handles_individual_errors(self):
        """Test that individual source errors don't stop the batch."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [
            {"url": "https://good.com", "title": "Good"},
            {"url": "https://bad.com", "title": "Bad"},  # Will cause error
            {"url": "https://good2.com", "title": "Good 2"},
        ]

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.count.return_value = 0
            mock_get_session.return_value = mock_session

            # The add method works for good URLs
            result = ResearchSourcesService.save_research_sources(
                "test-id", sources, username="testuser"
            )

            assert result == 3


class TestGetResearchSources:
    """Tests for get_research_sources method."""

    def test_get_research_sources_returns_list(self):
        """Test that method returns a list."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = []
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.get_research_sources(
                "test-id", username="testuser"
            )

            assert isinstance(result, list)
            assert len(result) == 0

    def test_get_research_sources_formats_correctly(self):
        """Test that resources are formatted correctly."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        mock_resource = MagicMock()
        mock_resource.id = 1
        mock_resource.url = "https://example.com"
        mock_resource.title = "Test Title"
        mock_resource.content_preview = "Preview text"
        mock_resource.source_type = "web"
        mock_resource.resource_metadata = {"key": "value"}
        mock_resource.created_at = "2024-01-01T00:00:00"

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = [
                mock_resource
            ]
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.get_research_sources(
                "test-id", username="testuser"
            )

            assert len(result) == 1
            assert result[0]["id"] == 1
            assert result[0]["url"] == "https://example.com"
            assert result[0]["title"] == "Test Title"
            assert result[0]["snippet"] == "Preview text"
            assert result[0]["source_type"] == "web"
            assert result[0]["metadata"] == {"key": "value"}

    def test_get_research_sources_handles_none_metadata(self):
        """Test handling resources with None metadata."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        mock_resource = MagicMock()
        mock_resource.id = 1
        mock_resource.url = "https://example.com"
        mock_resource.title = "Test"
        mock_resource.content_preview = None
        mock_resource.source_type = "web"
        mock_resource.resource_metadata = None
        mock_resource.created_at = None

        with patch(
            "local_deep_research.web.services.research_sources_service.get_user_db_session"
        ) as mock_get_session:
            mock_session = MagicMock()
            mock_session.__enter__ = Mock(return_value=mock_session)
            mock_session.__exit__ = Mock(return_value=False)
            mock_session.query.return_value.filter_by.return_value.order_by.return_value.all.return_value = [
                mock_resource
            ]
            mock_get_session.return_value = mock_session

            result = ResearchSourcesService.get_research_sources(
                "test-id", username="testuser"
            )

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


class TestUpdateResearchWithSources:
    """Tests for update_research_with_sources method."""

    def test_update_research_with_sources_success(self):
        """Test successful research update with sources."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        sources = [{"url": "https://example.com", "title": "Test"}]

        mock_research = MagicMock()
        mock_research.research_meta = {}

        with patch.object(
            ResearchSourcesService, "save_research_sources", return_value=1
        ):
            with patch(
                "local_deep_research.web.services.research_sources_service.get_user_db_session"
            ) as mock_get_session:
                mock_session = MagicMock()
                mock_session.__enter__ = Mock(return_value=mock_session)
                mock_session.__exit__ = Mock(return_value=False)
                mock_session.query.return_value.filter_by.return_value.first.return_value = mock_research
                mock_get_session.return_value = mock_session

                result = ResearchSourcesService.update_research_with_sources(
                    "test-id", sources, username="testuser"
                )

                assert result is True
                assert mock_research.research_meta["sources_count"] == 1
                assert mock_research.research_meta["has_sources"] is True

    def test_update_research_not_found(self):
        """Test update when research not found."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        with patch.object(
            ResearchSourcesService, "save_research_sources", return_value=0
        ):
            with patch(
                "local_deep_research.web.services.research_sources_service.get_user_db_session"
            ) as mock_get_session:
                mock_session = MagicMock()
                mock_session.__enter__ = Mock(return_value=mock_session)
                mock_session.__exit__ = Mock(return_value=False)
                mock_session.query.return_value.filter_by.return_value.first.return_value = None
                mock_get_session.return_value = mock_session

                result = ResearchSourcesService.update_research_with_sources(
                    "nonexistent-id", [], username="testuser"
                )

                assert result is False

    def test_update_research_initializes_none_metadata(self):
        """Test that None metadata is initialized."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        mock_research = MagicMock()
        mock_research.research_meta = None

        with patch.object(
            ResearchSourcesService, "save_research_sources", return_value=5
        ):
            with patch(
                "local_deep_research.web.services.research_sources_service.get_user_db_session"
            ) as mock_get_session:
                mock_session = MagicMock()
                mock_session.__enter__ = Mock(return_value=mock_session)
                mock_session.__exit__ = Mock(return_value=False)
                mock_session.query.return_value.filter_by.return_value.first.return_value = mock_research
                mock_get_session.return_value = mock_session

                result = ResearchSourcesService.update_research_with_sources(
                    "test-id", [], username="testuser"
                )

                assert result is True
                assert mock_research.research_meta == {
                    "sources_count": 5,
                    "has_sources": True,
                }

    def test_update_research_handles_exception(self):
        """Test that exceptions return False."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        with patch.object(
            ResearchSourcesService,
            "save_research_sources",
            side_effect=Exception("DB error"),
        ):
            result = ResearchSourcesService.update_research_with_sources(
                "test-id", [], username="testuser"
            )

            assert result is False


class TestResearchSourcesServiceClass:
    """Tests for ResearchSourcesService class."""

    def test_class_has_static_methods(self):
        """Test that class has required static methods."""
        from local_deep_research.web.services.research_sources_service import (
            ResearchSourcesService,
        )

        assert hasattr(ResearchSourcesService, "save_research_sources")
        assert hasattr(ResearchSourcesService, "get_research_sources")
        assert hasattr(ResearchSourcesService, "update_research_with_sources")

        assert callable(ResearchSourcesService.save_research_sources)
        assert callable(ResearchSourcesService.get_research_sources)
        assert callable(ResearchSourcesService.update_research_with_sources)
