"""Public router APIs: fluent builder and spec-based constructors."""

from __future__ import annotations

from collections.abc import Callable, Collection, Sequence
from enum import Enum

from fastapi import APIRouter
from sqlmodel import SQLModel
from typing_extensions import Self

from auen.config import (
    ALL_OPERATIONS,
    AuthConfig,
    FilterConfig,
    HooksConfig,
    NestedWriteConfig,
    Operation,
    PaginationConfig,
    SchemaConfig,
)
from auen.policy import CrudPolicy
from auen.repository import AsyncCrudRepository, AsyncSqlModelRepository
from auen.router_plan import CrudRouterSpec, resolve_router_plan, validate_specs
from auen.router_runtime import build_router_from_plan
from auen.types import SessionDep

__all__ = ["CrudRouterBuilder", "CrudRouterSpec", "build_crud_routers"]


def build_crud_routers(
    *,
    specs: Sequence[CrudRouterSpec],
    session_dep: Callable[..., SessionDep],
) -> list[APIRouter]:
    """Build one APIRouter for each CrudRouterSpec."""
    validate_specs(specs)
    return [
        build_router_from_plan(
            plan=resolve_router_plan(spec),
            session_dep=session_dep,
        )
        for spec in specs
    ]


class CrudRouterBuilder:
    """Fluent compatibility adapter that compiles options into CrudRouterSpec."""

    def __init__(self) -> None:
        self._schemas: SchemaConfig | None = None
        self._prefix: str = ""
        self._tags: list[str | Enum] = []
        self._operations: Collection[Operation] = ALL_OPERATIONS
        self._auth: AuthConfig | None = None
        self._policy: CrudPolicy | None = None
        self._id_field: str = ""
        self._pagination: PaginationConfig | None = None
        self._filters: FilterConfig | None = None
        self._repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository] = (
            AsyncSqlModelRepository
        )
        self._hooks: HooksConfig | None = None
        self._nested_writes: NestedWriteConfig | None = None
        self._nested_policy_registry: dict[type[SQLModel], CrudPolicy] = {}
        self._include_in_schema: set[Operation] | None = None
        self._operation_id_prefix: str = ""

    def with_schemas(self, schemas: SchemaConfig) -> Self:
        self._schemas = schemas
        return self

    def with_prefix(self, prefix: str) -> Self:
        self._prefix = prefix
        return self

    def with_tags(self, tags: Sequence[str | Enum]) -> Self:
        self._tags = list(tags)
        return self

    def with_operations(self, operations: Collection[Operation]) -> Self:
        self._operations = operations
        return self

    def with_auth(self, auth: AuthConfig) -> Self:
        self._auth = auth
        return self

    def with_policy(self, policy: CrudPolicy) -> Self:
        self._policy = policy
        return self

    def with_id_field(self, id_field: str) -> Self:
        self._id_field = id_field
        return self

    def with_pagination(self, pagination: PaginationConfig) -> Self:
        self._pagination = pagination
        return self

    def with_filters(self, filters: FilterConfig) -> Self:
        self._filters = filters
        return self

    def with_repository_factory(
        self,
        repository_factory: Callable[[type[SQLModel]], AsyncCrudRepository],
    ) -> Self:
        self._repository_factory = repository_factory
        return self

    def with_hooks(self, hooks: HooksConfig) -> Self:
        self._hooks = hooks
        return self

    def with_nested_writes(self, nested_writes: NestedWriteConfig) -> Self:
        self._nested_writes = nested_writes
        return self

    def with_nested_policy_registry(
        self, registry: dict[type[SQLModel], CrudPolicy]
    ) -> Self:
        self._nested_policy_registry = registry
        return self

    def with_include_in_schema(self, ops: set[Operation]) -> Self:
        self._include_in_schema = ops
        return self

    def with_operation_id_prefix(self, prefix: str) -> Self:
        self._operation_id_prefix = prefix
        return self

    def _build_specs(self, models: Sequence[type[SQLModel]]) -> list[CrudRouterSpec]:
        return [
            CrudRouterSpec(
                model=model,
                schemas=self._schemas,
                prefix=self._prefix,
                tags=tuple(self._tags),
                operations=self._operations,
                auth=self._auth,
                policy=self._policy,
                id_field=self._id_field,
                pagination=self._pagination,
                filters=self._filters,
                repository_factory=self._repository_factory,
                hooks=self._hooks,
                nested_writes=self._nested_writes,
                nested_policy_registry=self._nested_policy_registry,
                include_in_schema=self._include_in_schema,
                operation_id_prefix=self._operation_id_prefix,
            )
            for model in models
        ]

    def _validate_models(self, models: Sequence[type[SQLModel]]) -> None:
        if not models:
            msg = "build_routers() requires at least one model"
            raise ValueError(msg)
        for model in models:
            if not isinstance(model, type) or not issubclass(model, SQLModel):
                msg = "build_routers() expects SQLModel classes in models"
                raise TypeError(msg)

    def _validate_multi_model_constraints(self, models: Sequence[type[SQLModel]]) -> None:
        if len(models) <= 1:
            return
        if self._schemas is not None:
            msg = "with_schemas() is only supported when building exactly one model"
            raise ValueError(msg)
        if self._prefix:
            msg = "with_prefix() is only supported when building exactly one model"
            raise ValueError(msg)
        if self._operation_id_prefix:
            msg = (
                "with_operation_id_prefix() is only supported when building exactly one "
                "model"
            )
            raise ValueError(msg)

    def build_routers(
        self,
        *,
        models: Sequence[type[SQLModel]],
        session_dep: Callable[..., SessionDep],
    ) -> list[APIRouter]:
        """Build one APIRouter per model."""
        self._validate_models(models)
        self._validate_multi_model_constraints(models)
        specs = self._build_specs(models)
        return build_crud_routers(specs=specs, session_dep=session_dep)
