"""
Tests for LibraryService domain detection and query filter methods.

Covers:
- _is_pubmed_url edge cases (ncbi without /pmc, substring in hostname, etc.)
- _apply_domain_filter all 4 branches (arxiv, pubmed, other, custom)
- _apply_search_filter pattern wrapping and return values
"""

from unittest.mock import patch, MagicMock


# ============== Helper to create service ==============


def _make_service():
    from local_deep_research.research_library.services.library_service import (
        LibraryService,
    )

    with patch.object(LibraryService, "__init__", lambda self, username: None):
        service = LibraryService.__new__(LibraryService)
        service.username = "test_user"
    return service


# ============== _is_pubmed_url edge cases ==============


class TestIsPubmedUrlEdgeCases:
    """Edge cases for _is_pubmed_url beyond the basic positive tests."""

    def test_ncbi_without_pmc_path_returns_false(self):
        """ncbi.nlm.nih.gov without /pmc in path should be False."""
        service = _make_service()
        assert (
            service._is_pubmed_url("https://ncbi.nlm.nih.gov/gene/12345")
            is False
        )

    def test_ncbi_pubchem_path_returns_false(self):
        """ncbi.nlm.nih.gov/pubchem (non-pmc sub-path) should be False."""
        service = _make_service()
        assert (
            service._is_pubmed_url("https://ncbi.nlm.nih.gov/pubchem/123")
            is False
        )

    def test_pubmed_substring_in_hostname_returns_true(self):
        """Hostname containing 'pubmed' as substring matches the catch-all."""
        service = _make_service()
        # Line 100: "pubmed" in hostname
        assert (
            service._is_pubmed_url("https://mypubmed.example.com/article")
            is True
        )

    def test_pubmed_only_in_path_returns_false(self):
        """'pubmed' appearing only in the path (not hostname) should be False."""
        service = _make_service()
        assert service._is_pubmed_url("https://example.com/pubmed/123") is False

    def test_empty_hostname_url_returns_false(self):
        """URL with no hostname should return False via the guard."""
        service = _make_service()
        assert service._is_pubmed_url("://nohost") is False

    def test_www_pubmed_subdomain_returns_true(self):
        """www.pubmed.ncbi.nlm.nih.gov should match via 'pubmed' in hostname."""
        service = _make_service()
        assert (
            service._is_pubmed_url("https://www.pubmed.ncbi.nlm.nih.gov/12345")
            is True
        )


# ============== _apply_domain_filter ==============


class TestApplyDomainFilterArxiv:
    """Tests for _apply_domain_filter with domain='arxiv.org'."""

    def test_arxiv_filter_calls_like_with_arxiv_pattern(self):
        """Arxiv branch uses .like('%arxiv.org%')."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        result = service._apply_domain_filter(
            mock_query, mock_model, "arxiv.org"
        )

        mock_model.original_url.like.assert_called_once_with("%arxiv.org%")
        mock_query.filter.assert_called_once()
        assert result == mock_query.filter.return_value


class TestApplyDomainFilterPubmed:
    """Tests for _apply_domain_filter with domain='pubmed'."""

    def test_pubmed_filter_uses_or_with_both_patterns(self, mocker):
        """Pubmed branch creates OR with '%pubmed%' and '%ncbi.nlm.nih.gov%'."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mock_or = mocker.patch(
            "local_deep_research.research_library.services.library_service.or_",
            return_value=MagicMock(),
        )

        result = service._apply_domain_filter(mock_query, mock_model, "pubmed")

        # Verify both like patterns were created
        like_calls = mock_model.original_url.like.call_args_list
        patterns = [call[0][0] for call in like_calls]
        assert "%pubmed%" in patterns
        assert "%ncbi.nlm.nih.gov%" in patterns

        # or_() was called and result used in filter
        mock_or.assert_called_once()
        mock_query.filter.assert_called_once()
        assert result == mock_query.filter.return_value


class TestApplyDomainFilterOther:
    """Tests for _apply_domain_filter with domain='other'."""

    def test_other_filter_uses_and_with_negated_patterns(self, mocker):
        """'other' branch creates AND with 3 negated .like() patterns."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mock_and = mocker.patch(
            "local_deep_research.research_library.services.library_service.and_",
            return_value=MagicMock(),
        )

        result = service._apply_domain_filter(mock_query, mock_model, "other")

        # Three like calls for the negation patterns
        like_calls = mock_model.original_url.like.call_args_list
        patterns = [call[0][0] for call in like_calls]
        assert "%arxiv.org%" in patterns
        assert "%pubmed%" in patterns
        assert "%ncbi.nlm.nih.gov%" in patterns

        mock_and.assert_called_once()
        mock_query.filter.assert_called_once()
        assert result == mock_query.filter.return_value


class TestApplyDomainFilterCustom:
    """Tests for _apply_domain_filter else clause (custom domains)."""

    def test_custom_domain_uses_like_with_domain(self):
        """Custom domain falls into else clause with .like('%domain%')."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        result = service._apply_domain_filter(
            mock_query, mock_model, "custom.example.com"
        )

        mock_model.original_url.like.assert_called_once_with(
            "%custom.example.com%"
        )
        assert result == mock_query.filter.return_value

    def test_empty_domain_falls_into_else_clause(self):
        """Empty string domain falls into else clause."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        result = service._apply_domain_filter(mock_query, mock_model, "")

        mock_model.original_url.like.assert_called_once_with("%%")
        assert result == mock_query.filter.return_value


# ============== _apply_search_filter ==============


class TestApplySearchFilter:
    """Tests for _apply_search_filter method."""

    def test_search_filter_wraps_query_in_wildcards(self, mocker):
        """Search query is wrapped as %search_query%."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mocker.patch(
            "local_deep_research.research_library.services.library_service.or_",
            return_value=MagicMock(),
        )
        mock_resource = MagicMock()
        mocker.patch(
            "local_deep_research.research_library.services.library_service.ResearchResource",
            mock_resource,
        )

        service._apply_search_filter(
            mock_query, mock_model, "quantum computing"
        )

        # Verify all 3 model fields called with correct pattern
        expected_pattern = "%quantum computing%"
        mock_model.title.ilike.assert_called_once_with(expected_pattern)
        mock_model.authors.ilike.assert_called_once_with(expected_pattern)
        mock_model.doi.ilike.assert_called_once_with(expected_pattern)
        mock_resource.title.ilike.assert_called_once_with(expected_pattern)

    def test_search_filter_uses_four_ilike_conditions(self, mocker):
        """Search filter creates or_() with 4 ilike conditions."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mock_or = mocker.patch(
            "local_deep_research.research_library.services.library_service.or_",
            return_value=MagicMock(),
        )
        mock_resource = MagicMock()
        mocker.patch(
            "local_deep_research.research_library.services.library_service.ResearchResource",
            mock_resource,
        )

        service._apply_search_filter(mock_query, mock_model, "test")

        # or_() receives 4 arguments
        assert mock_or.call_count == 1
        args = mock_or.call_args[0]
        assert len(args) == 4

    def test_search_filter_special_chars_not_escaped(self, mocker):
        """Documents behavior: SQL special chars in query are not escaped."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mocker.patch(
            "local_deep_research.research_library.services.library_service.or_",
            return_value=MagicMock(),
        )
        mocker.patch(
            "local_deep_research.research_library.services.library_service.ResearchResource",
            MagicMock(),
        )

        service._apply_search_filter(mock_query, mock_model, "%test%")

        # Pattern wraps as-is: %%test%%
        mock_model.title.ilike.assert_called_once_with("%%test%%")

    def test_search_filter_returns_filtered_query(self, mocker):
        """Return value is the result of query.filter()."""
        service = _make_service()
        mock_query = MagicMock()
        mock_model = MagicMock()

        mocker.patch(
            "local_deep_research.research_library.services.library_service.or_",
            return_value=MagicMock(),
        )
        mocker.patch(
            "local_deep_research.research_library.services.library_service.ResearchResource",
            MagicMock(),
        )

        result = service._apply_search_filter(mock_query, mock_model, "test")

        assert result == mock_query.filter.return_value
