"""Router builder configuration tests."""

from __future__ import annotations

from typing import cast

import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from sqlmodel import Field, SQLModel
from tests.conftest import Book, SessionFactory

from auen import CrudRouterBuilder, Operation, derive_schemas
from auen.repository import AsyncSqlModelRepository


class NotAModel:
    pass


async def test_constructor_is_parameterless() -> None:
    builder = CrudRouterBuilder()
    assert isinstance(builder, CrudRouterBuilder)


async def test_builder_has_single_build_entrypoint() -> None:
    builder = CrudRouterBuilder()
    assert hasattr(builder, "build_routers")
    assert not hasattr(builder, "build")
    assert not hasattr(builder, "build_all")
    assert not hasattr(CrudRouterBuilder, "for_model")


async def test_build_routers_requires_models(get_session: SessionFactory) -> None:
    with pytest.raises(ValueError, match="at least one model"):
        CrudRouterBuilder().build_routers(models=[], session_dep=get_session)


async def test_build_routers_requires_sqlmodel_classes(
    get_session: SessionFactory,
) -> None:
    with pytest.raises(TypeError, match="SQLModel classes"):
        CrudRouterBuilder().build_routers(
            models=[cast(type[SQLModel], NotAModel)],
            session_dep=get_session,
        )


async def test_build_routers_multi_model_constraints(get_session: SessionFactory) -> None:
    class AnotherBook(SQLModel, table=True):
        __tablename__ = "another_book"
        id: int | None = Field(default=None, primary_key=True)

    with pytest.raises(ValueError, match="with_schemas"):
        CrudRouterBuilder().with_schemas(derive_schemas(Book)).build_routers(
            models=[Book, AnotherBook],
            session_dep=get_session,
        )
    with pytest.raises(ValueError, match="with_prefix"):
        CrudRouterBuilder().with_prefix("/x").build_routers(
            models=[Book, AnotherBook],
            session_dep=get_session,
        )
    with pytest.raises(ValueError, match="with_operation_id_prefix"):
        CrudRouterBuilder().with_operation_id_prefix("x").build_routers(
            models=[Book, AnotherBook],
            session_dep=get_session,
        )


async def test_build_routers_multi_model_defaults_are_allowed(
    get_session: SessionFactory,
) -> None:
    class AnotherBookAllowed(SQLModel, table=True):
        __tablename__ = "another_book_allowed"
        id: int | None = Field(default=None, primary_key=True)
        title: str

    routers = (
        CrudRouterBuilder()
        .with_operations({Operation.LIST})
        .build_routers(models=[Book, AnotherBookAllowed], session_dep=get_session)
    )

    assert len(routers) == 2
    assert routers[0].prefix == "/books"
    assert routers[1].prefix == "/anotherbookalloweds"


async def test_prefix_without_slash(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_prefix("mybooks")
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        resp = await c.get("/mybooks/")
        assert resp.status_code == 200


async def test_prefix_with_slash(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_prefix("/mybooks")
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        resp = await c.get("/mybooks/")
        assert resp.status_code == 200


async def test_include_in_schema(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_operations({Operation.LIST, Operation.CREATE})
        .with_include_in_schema({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    schema = app.openapi()
    assert "post" not in schema["paths"]["/books/"]
    assert "get" in schema["paths"]["/books/"]


async def test_operation_id_prefix(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_operations({Operation.LIST})
        .with_operation_id_prefix("custom")
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    schema = app.openapi()
    get_op = schema["paths"]["/books/"]["get"]
    assert get_op["operationId"] == "custom_list"


async def test_with_tags(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_tags(["MyTag"])
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    schema = app.openapi()
    get_op = schema["paths"]["/books/"]["get"]
    assert "MyTag" in get_op["tags"]


async def test_with_custom_schemas(get_session: SessionFactory) -> None:
    schemas = derive_schemas(Book)
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_schemas(schemas)
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        resp = await c.get("/books/")
        assert resp.status_code == 200


async def test_with_id_field(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_id_field("id")
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        resp = await c.get("/books/")
        assert resp.status_code == 200


async def test_with_repository_factory(get_session: SessionFactory) -> None:
    app = FastAPI()
    app.include_router(
        CrudRouterBuilder()
        .with_repository_factory(AsyncSqlModelRepository)
        .with_operations({Operation.LIST})
        .build_routers(models=[Book], session_dep=get_session)[0]
    )
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        resp = await c.get("/books/")
        assert resp.status_code == 200
