from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.agents.tools.faq import (
    get_restaurant_policies_impl,
    search_knowledge_base_impl,
)

# ---------------------------------------------------------------------------
# search_knowledge_base_impl
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_search_knowledge_base_returns_results(fake_ctx):
    mock_results = [
        {"content": "We're open 9-5.", "source": "faq", "score": 0.95},
        {"content": "Reservations accepted.", "source": "policy", "score": 0.87},
    ]
    with (
        patch(
            "app.agents.tools.faq.generate_embedding",
            new_callable=AsyncMock,
            return_value=[0.1] * 1536,
        ) as mock_embed,
        patch(
            "app.agents.tools.faq.hybrid_search",
            new_callable=AsyncMock,
            return_value=mock_results,
        ) as mock_search,
    ):
        result = await search_knowledge_base_impl(fake_ctx, "opening hours")

        assert len(result) == 2
        assert result[0]["content"] == "We're open 9-5."
        mock_embed.assert_awaited_once_with("opening hours")
        mock_search.assert_awaited_once()

        call_kwargs = mock_search.call_args.kwargs
        assert call_kwargs["restaurant_id"] == "test-restaurant-id"
        assert call_kwargs["limit"] == 5


@pytest.mark.asyncio
async def test_search_knowledge_base_with_source_filter(fake_ctx):
    with (
        patch(
            "app.agents.tools.faq.generate_embedding",
            new_callable=AsyncMock,
            return_value=[0.1] * 1536,
        ),
        patch(
            "app.agents.tools.faq.hybrid_search",
            new_callable=AsyncMock,
            return_value=[],
        ) as mock_search,
    ):
        result = await search_knowledge_base_impl(fake_ctx, "menu items", source="menu")

        assert result == []
        call_kwargs = mock_search.call_args.kwargs
        assert call_kwargs["source"] == "menu"


@pytest.mark.asyncio
async def test_search_knowledge_base_empty_results(fake_ctx):
    with (
        patch(
            "app.agents.tools.faq.generate_embedding",
            new_callable=AsyncMock,
            return_value=[0.1] * 1536,
        ),
        patch(
            "app.agents.tools.faq.hybrid_search",
            new_callable=AsyncMock,
            return_value=[],
        ),
    ):
        result = await search_knowledge_base_impl(fake_ctx, "something obscure")
        assert result == []


# ---------------------------------------------------------------------------
# get_restaurant_policies_impl — helpers
# ---------------------------------------------------------------------------


def _mock_restaurant(settings=None):
    """Create a mock restaurant with given settings."""
    r = MagicMock()
    r.id = "test-restaurant-id"
    r.settings = settings
    return r


def _mock_execute_result(scalar_value):
    result = MagicMock()
    result.scalar_one_or_none.return_value = scalar_value
    return result


# ---------------------------------------------------------------------------
# get_restaurant_policies_impl — tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_policies_with_settings(fake_ctx):
    restaurant = _mock_restaurant(
        settings={
            "min_advance_hours": 2,
            "max_advance_days": 60,
            "max_party_size": 10,
            "cancellation_policy": "Free cancellation up to 24h before.",
        }
    )
    fake_ctx.deps.session.execute.return_value = _mock_execute_result(restaurant)

    result = await get_restaurant_policies_impl(fake_ctx)

    assert result["min_advance_hours"] == 2
    assert result["max_advance_days"] == 60
    assert result["max_party_size"] == 10
    assert result["cancellation_policy"] == "Free cancellation up to 24h before."


@pytest.mark.asyncio
async def test_policies_with_empty_settings(fake_ctx):
    restaurant = _mock_restaurant(settings={})
    fake_ctx.deps.session.execute.return_value = _mock_execute_result(restaurant)

    result = await get_restaurant_policies_impl(fake_ctx)

    assert result["min_advance_hours"] == 1
    assert result["max_advance_days"] == 90
    assert result["max_party_size"] == 20
    assert result["cancellation_policy"] is None


@pytest.mark.asyncio
async def test_policies_with_none_settings(fake_ctx):
    restaurant = _mock_restaurant(settings=None)
    fake_ctx.deps.session.execute.return_value = _mock_execute_result(restaurant)

    result = await get_restaurant_policies_impl(fake_ctx)

    assert result["min_advance_hours"] == 1


@pytest.mark.asyncio
async def test_policies_restaurant_not_found(fake_ctx):
    fake_ctx.deps.session.execute.return_value = _mock_execute_result(None)

    result = await get_restaurant_policies_impl(fake_ctx)

    assert result["min_advance_hours"] == 1
    assert result["cancellation_policy"] is None
