"""
Shared pytest fixtures.

Integration tests that need a real database should use a separate PostgreSQL
instance (e.g. a Docker service spun up by the CI job) and set DATABASE_URL
in the environment before importing the app.

For unit tests of pure functions no database is needed at all.

The fixtures below are the ONE definition of three things that used to be
copied into a dozen test modules each (#2825). They are deliberately not
autouse: a module opts in with
``pytestmark = pytest.mark.usefixtures("<name>")`` (or a test names the
fixture as a parameter), so a unit test that never touches the engine or the
MCP context pays nothing for them.
"""
import os
from unittest.mock import AsyncMock, patch

import pytest
import pytest_asyncio


@pytest.fixture(autouse=True)
def _isolate_env(request, monkeypatch):
    """Prevent unit tests from accidentally reading production env vars.

    Integration tests (marked `integration`) are skipped here: they must use the
    real DATABASE_URL injected by the CI integration lane, not the fake one.
    """
    if request.node.get_closest_marker("integration"):
        return
    monkeypatch.setenv("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")
    monkeypatch.setenv("SECRET_KEY", "test-secret-key")
    monkeypatch.setenv("OLLAMA_URL", "http://localhost:11434")


@pytest.fixture
def _bind_user():
    """Bind MCP caller #7 for the duration of a test.

    The MCP tool layer reads the caller from a ContextVar the HTTP transport
    sets per request; a unit test of a tool has no request, so it binds the
    caller itself. Every tool-layer test module opts in with
    ``pytestmark = pytest.mark.usefixtures("_bind_user")`` and builds its fakes
    with user_id=7 so ownership checks see the caller as the owner.
    """
    from scribe.mcp._context import _user_id_ctx

    token = _user_id_ctx.set(7)
    yield
    _user_id_ctx.reset(token)


@pytest_asyncio.fixture
async def _dispose_engine():
    """Dispose the app's module-level engine after a test that hit Postgres.

    The engine pools asyncpg connections per event loop, but pytest-asyncio
    runs each test on a fresh loop — so without this, test 2 gets handed
    test 1's connection bound to a now-dead loop ("Future attached to a
    different loop"). Disposing in the test's own loop teardown clears the
    pool cleanly. The import is deferred so merely collecting a module that
    mixes unit and integration tests never builds an engine.
    """
    from scribe.models import engine

    yield
    await engine.dispose()


@pytest.fixture
def _no_supersession():
    """Stub the auto-inject menu's "which lines are superseded?" lookup (#278).

    That is a real database call on a path the plugin-context tests exercise
    without one. Stubbed to "nothing superseded" — the ordinary state — rather
    than hidden behind a try/except in the product, which would make the code
    lie about what it does. The label's own behaviour is covered in
    tests/test_supersession_ranking.py.
    """
    with patch("scribe.services.plugin_context.superseded_ids",
               AsyncMock(return_value=set())):
        yield
