"""Shared fixtures for auen tests."""

from __future__ import annotations

from collections.abc import AsyncGenerator, Callable
from typing import Annotated, cast

import pytest
from fastapi import FastAPI, Header, HTTPException
from httpx import ASGITransport, AsyncClient
from pydantic import BaseModel
from sqlalchemy import inspect
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from sqlalchemy.pool import StaticPool
from sqlalchemy.sql import ColumnElement
from sqlmodel import Field, SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession

from auen import (
    AuthConfig,
    CrudRouterBuilder,
    FilterConfig,
    FilterFieldConfig,
    FilterOp,
    PaginationConfig,
)
from auen.types import SelectQuery, User

SessionFactory = Callable[[], AsyncGenerator[AsyncSession, None]]

# --- Domain model ---


class Book(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    isbn: str
    pages: int | None = None
    owner_id: str | None = None


# --- Engine & session ---


@pytest.fixture
async def engine() -> AsyncGenerator[AsyncEngine, None]:
    eng = create_async_engine(
        "sqlite+aiosqlite://",
        echo=False,
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    async with eng.begin() as conn:
        await conn.run_sync(SQLModel.metadata.create_all)
    yield eng
    await eng.dispose()


@pytest.fixture
async def get_session(engine: AsyncEngine) -> SessionFactory:
    async def _get_session() -> AsyncGenerator[AsyncSession, None]:
        async with AsyncSession(engine) as session:
            yield session

    return _get_session


# --- Auth fixtures ---


def get_current_user(
    x_user: Annotated[str | None, Header()] = None,
) -> str:
    if x_user is None:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return x_user


AUTH_CONFIG = AuthConfig(dependency=get_current_user)


# --- Policy fixture ---


class OwnerPolicy:
    """Only allow access to books owned by the current user."""

    def can_create(self, user: User, obj_in: BaseModel) -> bool:
        return True

    def can_read(self, user: User, db_obj: SQLModel) -> bool:
        return getattr(db_obj, "owner_id", None) == user

    def can_update(self, user: User, db_obj: SQLModel, obj_in: BaseModel) -> bool:
        return getattr(db_obj, "owner_id", None) == user

    def can_delete(self, user: User, db_obj: SQLModel) -> bool:
        return getattr(db_obj, "owner_id", None) == user

    def filter_list_query(
        self, user: User, query: SelectQuery[SQLModel]
    ) -> SelectQuery[SQLModel]:
        owner_col = cast(ColumnElement[str], inspect(Book).columns["owner_id"])
        return query.where(owner_col == cast(str, user))


# --- App factories ---


@pytest.fixture
def app(get_session: SessionFactory) -> FastAPI:
    """Basic app with no auth."""
    application = FastAPI()
    application.include_router(CrudRouterBuilder.for_model(Book, get_session).build())
    return application


@pytest.fixture
async def client(app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client


@pytest.fixture
def auth_app(get_session: SessionFactory) -> FastAPI:
    """App with auth required."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session).with_auth(AUTH_CONFIG).build()
    )
    return application


@pytest.fixture
async def auth_client(auth_app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=auth_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client


@pytest.fixture
async def authed_client(auth_app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=auth_app)
    async with AsyncClient(
        transport=transport,
        base_url="http://test",
        headers={"X-User": "alice"},
    ) as client:
        yield client


@pytest.fixture
def policy_app(get_session: SessionFactory) -> FastAPI:
    """App with auth + owner policy."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session)
        .with_auth(AUTH_CONFIG)
        .with_policy(OwnerPolicy())
        .build()
    )
    return application


@pytest.fixture
async def policy_client(policy_app: FastAPI) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=policy_app)
    async with AsyncClient(
        transport=transport,
        base_url="http://test",
        headers={"X-User": "alice"},
    ) as client:
        yield client


@pytest.fixture
def pagination_app(get_session: SessionFactory) -> FastAPI:
    """App with custom pagination."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session)
        .with_pagination(PaginationConfig(default_limit=2, max_limit=5))
        .with_filters(
            FilterConfig(
                fields={"title": FilterFieldConfig(ops=frozenset({FilterOp.EQ}))},
                sort_fields=["title", "pages"],
            )
        )
        .build()
    )
    return application


@pytest.fixture
async def pagination_client(
    pagination_app: FastAPI,
) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=pagination_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client


@pytest.fixture
def page_pagination_app(get_session: SessionFactory) -> FastAPI:
    """App with page-style pagination."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session)
        .with_pagination(
            PaginationConfig(
                default_limit=2,
                max_limit=5,
                style="page",
            )
        )
        .with_filters(
            FilterConfig(
                fields={"title": FilterFieldConfig(ops=frozenset({FilterOp.EQ}))},
                sort_fields=["title"],
            )
        )
        .build()
    )
    return application


@pytest.fixture
async def page_pagination_client(
    page_pagination_app: FastAPI,
) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=page_pagination_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client


@pytest.fixture
def filter_ops_app(get_session: SessionFactory) -> FastAPI:
    """App with richer filter operations."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session)
        .with_filters(
            FilterConfig(
                fields={
                    "pages": FilterFieldConfig(
                        ops=frozenset(
                            {
                                FilterOp.EQ,
                                FilterOp.GT,
                                FilterOp.LT,
                                FilterOp.GTE,
                                FilterOp.LTE,
                                FilterOp.IN,
                            }
                        )
                    ),
                    "title": FilterFieldConfig(
                        ops=frozenset({FilterOp.EQ, FilterOp.CONTAINS})
                    ),
                },
                sort_fields=["title", "pages"],
            )
        )
        .build()
    )
    return application


@pytest.fixture
async def filter_ops_client(
    filter_ops_app: FastAPI,
) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=filter_ops_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client


@pytest.fixture
def envelope_pagination_app(get_session: SessionFactory) -> FastAPI:
    """App with pagination envelope response."""
    application = FastAPI()
    application.include_router(
        CrudRouterBuilder.for_model(Book, get_session)
        .with_pagination(
            PaginationConfig(
                default_limit=2,
                max_limit=5,
                include_total=True,
            )
        )
        .with_filters(
            FilterConfig(
                fields={"title": FilterFieldConfig(ops=frozenset({FilterOp.EQ}))},
                sort_fields=["title"],
            )
        )
        .build()
    )
    return application


@pytest.fixture
async def envelope_pagination_client(
    envelope_pagination_app: FastAPI,
) -> AsyncGenerator[AsyncClient, None]:
    transport = ASGITransport(app=envelope_pagination_app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        yield client
