from unittest.mock import MagicMock

import pytest
from fastapi import HTTPException

from app.agents.prompts import custom_prompt_suffix
from app.models.restaurant import Restaurant, RestaurantPatch
from app.routers.restaurants import patch_my_restaurant
from app.schemas.agent_config import (
    AgentConfig,
    AgentSettings,
    get_agent_config,
    get_enabled_agents,
)

# --- AgentConfig / AgentSettings validation ---


def test_agent_config_defaults():
    c = AgentConfig()
    assert c.enabled is True
    assert c.custom_prompt_suffix == ""


def test_agent_config_with_values():
    c = AgentConfig(enabled=False, custom_prompt_suffix="Be extra polite")
    assert c.enabled is False
    assert c.custom_prompt_suffix == "Be extra polite"


def test_agent_settings_defaults():
    s = AgentSettings()
    assert s.reservation.enabled is True
    assert s.faq.enabled is True
    assert s.takeaway.enabled is False  # takeaway is opt-in by default


def test_agent_settings_partial_update():
    s = AgentSettings(takeaway=AgentConfig(enabled=True))
    assert s.reservation.enabled is True
    assert s.takeaway.enabled is True


# --- get_agent_config ---


def test_get_agent_config_none_settings():
    assert get_agent_config(None, "faq") == AgentConfig()


def test_get_agent_config_missing_agents_key():
    assert get_agent_config({"other": 1}, "faq") == AgentConfig()


def test_get_agent_config_valid():
    settings = {"agents": {"faq": {"enabled": False, "custom_prompt_suffix": "test"}}}
    config = get_agent_config(settings, "faq")
    assert config.enabled is False
    assert config.custom_prompt_suffix == "test"


def test_get_agent_config_unknown_type_returns_default():
    settings: dict = {"agents": {}}
    assert get_agent_config(settings, "nonexistent") == AgentConfig()


# --- get_enabled_agents ---


def test_enabled_agents_none_settings():
    # None settings → faq + reservation only (takeaway is opt-in)
    assert get_enabled_agents(None) == {"reservation", "faq"}


def test_enabled_agents_empty_settings():
    # Empty dict, no agents key, no flat flags → faq + reservation
    assert get_enabled_agents({}) == {"reservation", "faq"}


def test_enabled_agents_flat_takeaway_enabled():
    # Flat takeaway_enabled=True → adds takeaway
    settings = {"takeaway_enabled": True}
    result = get_enabled_agents(settings)
    assert result == {"reservation", "faq", "takeaway"}


def test_enabled_agents_flat_takeaway_disabled():
    # Flat takeaway_enabled=False → no takeaway
    settings = {"takeaway_enabled": False}
    result = get_enabled_agents(settings)
    assert "takeaway" not in result
    assert "reservation" in result
    assert "faq" in result


def test_enabled_agents_structured_one_disabled():
    settings = {"agents": {"takeaway": {"enabled": False}}}
    result = get_enabled_agents(settings)
    assert "takeaway" not in result
    assert "reservation" in result
    assert "faq" in result


def test_enabled_agents_structured_overrides_flat():
    # Structured agents key takes precedence over flat flags
    settings = {"takeaway_enabled": False, "agents": {"takeaway": {"enabled": True}}}
    result = get_enabled_agents(settings)
    assert "takeaway" in result


# --- custom_prompt_suffix ---


def test_empty_suffix_returns_empty():
    ctx = MagicMock()
    ctx.deps.agent_config.custom_prompt_suffix = ""
    assert custom_prompt_suffix(ctx) == ""


def test_whitespace_suffix_returns_empty():
    ctx = MagicMock()
    ctx.deps.agent_config.custom_prompt_suffix = "   "
    assert custom_prompt_suffix(ctx) == ""


def test_non_empty_suffix_returns_prefixed():
    ctx = MagicMock()
    ctx.deps.agent_config.custom_prompt_suffix = "Be extra polite"
    result = custom_prompt_suffix(ctx)
    assert result.startswith("Additional instructions")
    assert "Be extra polite" in result


# --- restaurant PATCH agent settings validation ---

pytestmark = pytest.mark.anyio


async def test_patch_restaurant_rejects_invalid_agent_settings(mock_session):
    restaurant = Restaurant(
        id="rest-1",
        name="Test Restaurant",
        slug="test-restaurant",
        team_id="team-1",
        settings={"timezone": "Europe/Brussels"},
    )
    payload = RestaurantPatch(
        settings={
            "agents": {
                "faq": {"custom_prompt_suffix": 123},
            }
        }
    )

    with pytest.raises(HTTPException) as exc_info:
        await patch_my_restaurant(payload=payload, restaurant=restaurant, session=mock_session)

    assert exc_info.value.status_code == 422
    assert exc_info.value.detail[0]["loc"] == ("faq", "custom_prompt_suffix")  # type: ignore[index]
    mock_session.add.assert_not_called()
    mock_session.commit.assert_not_awaited()
    mock_session.refresh.assert_not_awaited()


async def test_patch_restaurant_accepts_valid_agent_settings(mock_session):
    restaurant = Restaurant(
        id="rest-1",
        name="Test Restaurant",
        slug="test-restaurant",
        team_id="team-1",
        settings={"timezone": "Europe/Brussels"},
    )
    payload = RestaurantPatch(
        settings={
            "agents": {
                "faq": {
                    "enabled": False,
                    "custom_prompt_suffix": "Answer briefly",
                }
            }
        }
    )

    result = await patch_my_restaurant(
        payload=payload,
        restaurant=restaurant,
        session=mock_session,
    )

    assert result is restaurant
    assert restaurant.settings == {
        "timezone": "Europe/Brussels",
        "agents": {
            "faq": {
                "enabled": False,
                "custom_prompt_suffix": "Answer briefly",
            }
        },
    }
    mock_session.add.assert_called_once_with(restaurant)
    mock_session.commit.assert_awaited_once_with()
    mock_session.refresh.assert_awaited_once_with(restaurant)


async def test_patch_restaurant_skips_validation_when_agents_unchanged(mock_session):
    restaurant = Restaurant(
        id="rest-1",
        name="Test Restaurant",
        slug="test-restaurant",
        team_id="team-1",
        settings={"timezone": "Europe/Brussels", "agents": {"faq": {"enabled": True}}},
    )
    payload = RestaurantPatch(
        settings={
            "notification_preferences": {
                "in_app_enabled": False,
            }
        }
    )

    result = await patch_my_restaurant(
        payload=payload,
        restaurant=restaurant,
        session=mock_session,
    )

    assert result is restaurant
    assert restaurant.settings == {
        "timezone": "Europe/Brussels",
        "agents": {"faq": {"enabled": True}},
        "notification_preferences": {
            "in_app_enabled": False,
        },
    }
    mock_session.add.assert_called_once_with(restaurant)
    mock_session.commit.assert_awaited_once_with()
    mock_session.refresh.assert_awaited_once_with(restaurant)


# --- Agent persona: first-person plural voice ---


def _make_prompt_ctx(capabilities: set[str]) -> MagicMock:
    ctx = MagicMock()
    ctx.deps.enabled_capabilities = capabilities
    return ctx


def _assert_no_third_person_restaurant(text: str) -> None:
    """The agent IS the restaurant; never speak about it in third person.

    Lock in the persona contract so the system prompt can't silently drift
    back to outsider phrasing — the model has been observed parroting
    'het restaurant vraagt…' / 'the restaurant requires…' verbatim.
    """
    forbidden = (
        "the restaurant requires",
        "the restaurant offers",
        "the restaurant accepts",
        "the restaurant is closed",
        "the restaurant is open",
        "by the restaurant team",
        "from the restaurant",
        "connect the guest with the restaurant",
    )
    lowered = text.lower()
    for phrase in forbidden:
        assert phrase not in lowered, f"system prompt contains outsider phrasing: {phrase!r}"


@pytest.mark.asyncio
async def test_reservation_prompt_speaks_in_first_person_plural() -> None:
    from app.agents.restaurant import _reservation_prompt

    prompt = await _reservation_prompt(_make_prompt_ctx({"reservation"}))
    assert prompt is not None

    _assert_no_third_person_restaurant(prompt)
    # First-person plural markers — the prompt MUST address the guest as
    # "us"/"we" and frame the dish-selection requirement as ours.
    assert "with us" in prompt
    assert "we'll send a confirmation" in prompt
    assert "we ask guests to pre-select" in prompt


@pytest.mark.asyncio
async def test_reservation_prompt_empty_when_capability_disabled() -> None:
    from app.agents.restaurant import _reservation_prompt

    assert await _reservation_prompt(_make_prompt_ctx(set())) == ""


@pytest.mark.asyncio
async def test_takeaway_prompt_speaks_in_first_person_plural() -> None:
    from app.agents.restaurant import _takeaway_prompt

    prompt = await _takeaway_prompt(_make_prompt_ctx({"takeaway"}))
    assert prompt is not None

    _assert_no_third_person_restaurant(prompt)
    assert "our menu" in prompt
