"""Advanced search engine service tests"""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.mark.asyncio
async def test_search_empty_db():
    """Empty query on empty DB -> returns empty results"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()

    # Mock empty count and empty items
    mock_count = MagicMock()
    mock_count.scalar.return_value = 0
    mock_items = MagicMock()
    mock_items.scalars.return_value.all.return_value = []

    db.execute.side_effect = [mock_count, mock_items]

    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, query="", page=1, page_size=20)
    assert result["total"] == 0
    assert result["items"] == []
    assert result["page"] == 1


@pytest.mark.asyncio
async def test_search_title_field():
    """Title field search generates correct ilike condition"""
    from app.services.search_engine import AdvancedSearchEngine
    condition = AdvancedSearchEngine._field_condition("title", "cancer", False)
    assert condition is not None


@pytest.mark.asyncio
async def test_search_abstract_field():
    """Abstract field search generates correct ilike condition"""
    from app.services.search_engine import AdvancedSearchEngine
    condition = AdvancedSearchEngine._field_condition("abstract", "test", False)
    assert condition is not None


@pytest.mark.asyncio
async def test_search_journal_field():
    """Journal field search"""
    from app.services.search_engine import AdvancedSearchEngine
    condition = AdvancedSearchEngine._field_condition("journal", "Nature", False)
    assert condition is not None


@pytest.mark.asyncio
async def test_search_all_field():
    """'all' field searches title OR abstract"""
    from app.services.search_engine import AdvancedSearchEngine
    condition = AdvancedSearchEngine._field_condition("all", "cancer", False)
    assert condition is not None


@pytest.mark.asyncio
async def test_search_exact_phrase():
    """Exact phrase disables % wildcards"""
    from app.services.search_engine import AdvancedSearchEngine
    # exact=True -> no % wrapping
    condition = AdvancedSearchEngine._field_condition("title", "EGFR mutation", True)
    assert condition is not None


@pytest.mark.asyncio
async def test_search_year_filter():
    """Year_from and year_to generate bounds"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()

    mock_count = MagicMock()
    mock_count.scalar.return_value = 0
    mock_items = MagicMock()
    mock_items.scalars.return_value.all.return_value = []

    db.execute.side_effect = [mock_count, mock_items]

    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(
            db, query="test", year_from=2020, year_to=2024, page=1, page_size=20
        )
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_boolean_or():
    """OR boolean combines terms with or_"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()

    mock_count = MagicMock()
    mock_count.scalar.return_value = 0
    mock_items = MagicMock()
    mock_items.scalars.return_value.all.return_value = []

    db.execute.side_effect = [mock_count, mock_items]

    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(
            db, query="cancer lung", boolean="or", page=1, page_size=20
        )
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_date_range():
    """Date_from and date_to filters"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()

    mock_count = MagicMock()
    mock_count.scalar.return_value = 0
    mock_items = MagicMock()
    mock_items.scalars.return_value.all.return_value = []

    db.execute.side_effect = [mock_count, mock_items]

    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(
            db, query="test",
            date_from="2024-01-01", date_to="2024-12-31",
            page=1, page_size=20
        )
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_page_size():
    """Custom page_size is respected"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()

    mock_count = MagicMock()
    mock_count.scalar.return_value = 100

    mock_result = MagicMock()
    mock_result.all.return_value = []
    mock_items = MagicMock()
    mock_items.scalars.return_value.all.return_value = []

    # count, tag lookups (0), journal lookups (0), items
    db.execute.side_effect = [mock_count, mock_result, mock_result, mock_items]

    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(
            db, query="test", page=2, page_size=5
        )
    assert result["page"] == 2
    assert result["page_size"] == 5



# ── Retracted / negative result filter tests ──


@pytest.mark.asyncio
async def test_search_retracted_yes():
    """retracted='yes' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, retracted="yes", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_retracted_no():
    """retracted='no' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, retracted="no", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_retracted_only():
    """retracted='only' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, retracted="only", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_negative_yes():
    """negative_result='yes' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, negative_result="yes", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_negative_no():
    """negative_result='no' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, negative_result="no", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_negative_only():
    """negative_result='only' filter does not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(db, negative_result="only", page=1, page_size=20)
    assert result["total"] == 0


@pytest.mark.asyncio
async def test_search_retracted_and_negative():
    """Combined retracted + negative_result filters do not error"""
    from app.services.search_engine import AdvancedSearchEngine
    db = AsyncMock()
    mock_count = MagicMock(); mock_count.scalar.return_value = 0
    mock_items = MagicMock(); mock_items.scalars.return_value.all.return_value = []
    db.execute.side_effect = [mock_count, mock_items]
    with patch("app.services.tag_loader.load_tags_for_literature", AsyncMock(return_value={})):
        result = await AdvancedSearchEngine.search(
            db, retracted="yes", negative_result="only", page=1, page_size=20
        )
    assert result["total"] == 0


__all__ = []
