from typing import (
    Any,
    Callable,
    Dict,
    List,
    NamedTuple,
    Optional,
    Protocol,
    Sequence,
    Tuple,
    Type,
    TypeVar,
    Union,
    overload,
)

from django.core.paginator import Page, Paginator
from django.db.models import Model, QuerySet
from typing_extensions import TypedDict

from coreapi import Field as CoreAPIField
from mongoengine import QuerySet as MongoQuerySet
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

def _positive_int(integer_string: str, strict: bool = ..., cutoff: Optional[int] = ...) -> int: ...
def _divide_with_ceil(a: int, b: int) -> int: ...
def _get_displayed_page_numbers(current: int, final: int) -> List[Optional[int]]: ...
def _get_page_links(
    page_numbers: Sequence[Optional[int]], current: int, url_func: Callable[[int], str]
) -> List[PageLink]: ...
def _reverse_ordering(ordering_tuple: Sequence[str]) -> Tuple[str, ...]: ...

class Cursor(NamedTuple):
    offset: int
    reverse: bool
    position: Optional[int]

class PageLink(NamedTuple):
    url: Optional[str]
    number: Optional[int]
    is_active: bool
    is_break: bool

class HtmlContext(TypedDict):
    previous_url: str
    next_url: str

class HtmlContextWithPageLinks(HtmlContext):
    page_links: List[PageLink]

PAGE_BREAK: PageLink = ...

_MT = TypeVar("_MT", bound=Model)

class BasePagination:
    display_page_controls: bool = ...
    def get_paginated_response_schema(self, schema: Any) -> Any: ...
    def get_paginated_response(self, data: Any) -> Response: ...
    def get_results(self, data: Dict[str, Any]) -> Any: ...
    def get_schema_fields(self, view: APIView) -> List[Any]: ...
    def get_schema_operation_parameters(self, view: APIView) -> List[Any]: ...
    def paginate_queryset(self, queryset: QuerySet[Any], request: Request, view: Optional[APIView] = ...) -> Any: ...
    def to_html(self) -> str: ...

class PageNumberPagination(BasePagination):
    display_page_controls: bool = ...
    django_paginator_class: Type[Paginator] = ...
    invalid_page_message: str = ...
    last_page_strings: Sequence[str] = ...
    max_page_size: Optional[int] = ...
    page_query_description: str = ...
    page_query_param: str = ...
    page_size_query_description: str = ...
    page_size_query_param: Optional[str] = ...
    page_size: Optional[int] = ...
    page: Optional[Page] = ...
    request: Optional[Request] = ...
    template: str = ...
    def paginate_queryset(
        self, queryset: Union[QuerySet[Any], MongoQuerySet[Any]], request: Request, view: Optional[APIView] = ...
    ) -> Optional[List[Page]]: ...
    def get_paginated_response_schema(self, schema: Dict[str, Any]) -> Dict[str, Any]: ...
    def get_schema_fields(self, view: APIView) -> List[CoreAPIField]: ...
    def get_schema_operation_parameters(self, view: APIView) -> List[Dict[str, Any]]: ...
    def get_page_size(self, request: Request) -> Optional[int]: ...
    def get_next_link(self) -> Optional[str]: ...
    def get_previous_link(self) -> Optional[str]: ...
    def get_html_context(self) -> HtmlContextWithPageLinks: ...

class CountProtocol(Protocol):
    def count(self) -> int: ...

_T = TypeVar("_T")
_D = TypeVar("_D", bound=Model)

class LimitOffsetPagination(BasePagination):
    count: Optional[int] = ...
    default_limit: Optional[int] = ...
    limit_query_description: str = ...
    limit_query_param: str = ...
    limit: Optional[int] = ...
    max_limit: Optional[int] = ...
    offset_query_description: str = ...
    offset_query_param: str = ...
    offset: Optional[int] = ...
    request: Optional[Request] = ...
    template: str = ...
    @overload
    def paginate_queryset(
        self, queryset: QuerySet[_D], request: Request, view: Optional[APIView] = ...
    ) -> Optional[List[_D]]: ...
    @overload
    def paginate_queryset(
        self, queryset: MongoQuerySet[_T], request: Request, view: Optional[APIView] = ...
    ) -> Optional[List[_T]]: ...
    def get_limit(self, request: Request) -> Optional[int]: ...
    def get_offset(self, request: Request) -> int: ...
    def get_next_link(self) -> Optional[str]: ...
    def get_previous_link(self) -> Optional[str]: ...
    def get_html_context(self) -> HtmlContextWithPageLinks: ...
    def get_count(self, queryset: CountProtocol) -> int: ...

class CursorPagination(BasePagination):
    base_url: Optional[str] = ...
    cursor_query_description: str = ...
    cursor_query_param: str = ...
    cursor: Optional[Cursor] = ...
    has_next: Optional[bool] = ...
    has_previous: Optional[bool] = ...
    invalid_cursor_message: str = ...
    max_page_size: Optional[int] = ...
    next_position: Optional[str] = ...
    offset_cutoff: int = ...
    ordering: Union[str, List[str], Tuple[str, ...]] = ...
    page_size_query_description: str = ...
    page_size_query_param: Optional[str] = ...
    page_size: Optional[int] = ...
    page: Optional[List[Any]] = ...
    previous_position: Optional[str] = ...
    template: str = ...
    @overload
    def paginate_queryset(
        self, queryset: QuerySet[_D], request: Request, view: Optional[APIView] = ...
    ) -> Optional[List[_D]]: ...
    @overload
    def paginate_queryset(
        self, queryset: MongoQuerySet[_T], request: Request, view: Optional[APIView] = ...
    ) -> Optional[List[_T]]: ...
    def get_page_size(self, request: Request) -> Optional[int]: ...
    def get_next_link(self) -> Optional[str]: ...
    def get_previous_link(self) -> Optional[str]: ...
    def get_html_context(self) -> HtmlContext: ...
    def get_ordering(self, request: Request, queryset: QuerySet[Any], view: APIView) -> Tuple[str, ...]: ...
    def decode_cursor(self, request: Request) -> Optional[Cursor]: ...
    def encode_cursor(self, cursor: Cursor) -> str: ...
    def _get_position_from_instance(self, instance: Any, ordering: Sequence[str]) -> str: ...
