from unittest.mock import AsyncMock, MagicMock

import pytest

from app.agents.deps import AgentDeps, CallerIdentity
from app.schemas.agent_config import AgentConfig


@pytest.fixture
def mock_session():
    """AsyncSession mock that records execute calls."""
    session = AsyncMock()
    session.add = MagicMock()
    session.flush = AsyncMock()
    session.commit = AsyncMock()
    session.refresh = AsyncMock()
    # `session.merge(obj, …)` re-attaches `obj` to this session. Tests
    # generally pass real model instances and expect the returned object
    # to behave identically — preserve the input so downstream attribute
    # access (e.g. `restaurant.settings`) still hits the real value.
    session.merge = AsyncMock(side_effect=lambda obj, *_a, **_kw: obj)
    return session


@pytest.fixture
def mock_http_client():
    """httpx.AsyncClient mock."""
    return AsyncMock()


@pytest.fixture
def agent_deps(mock_session, mock_http_client):
    """AgentDeps with mocked externals."""
    return AgentDeps(
        session=mock_session,
        restaurant_id="test-restaurant-id",
        http_client=mock_http_client,
        caller=CallerIdentity(
            channel="dashboard",
            verified=True,
            identity_key="test-user-id",
            conversation_id="test-conversation-id",
        ),
        language="nl",
        agent_config=AgentConfig(),
    )


@pytest.fixture
def fake_ctx(agent_deps):
    """RunContext mock with .deps pointing at agent_deps."""
    ctx = MagicMock()
    ctx.deps = agent_deps
    return ctx
