from collections.abc import Callable
from functools import partial, wraps
from typing import ParamSpecArgs, ParamSpecKwargs

from django.http import HttpRequest
from django.views.decorators.cache import cache_page as dj_cache_page

from core.models import Config

VaryByFunc = Callable[[HttpRequest, ParamSpecArgs, ParamSpecKwargs], str]


def vary_by_ap_json(request, *args, **kwargs) -> str:
    """
    Return a cache usable string token that is different based upon Accept
    header.
    """
    if request.ap_json:
        return "ap_json"
    return "not_ap"


def vary_by_identity(request, *args, **kwargs) -> str:
    """
    Return a cache usable string token that is different based upon the
    request.identity
    """
    if request.identity:
        return f"ident{request.identity.pk}"
    return "identNone"


def cache_page(
    timeout: int | str = "cache_timeout_page_default",
    *,
    key_prefix: str = "",
    public_only: bool = False,
    vary_by: VaryByFunc | list[VaryByFunc] | None = None,
):
    """
    Decorator for views that caches the page result.
    timeout can either be the number of seconds or the name of a SystemOptions
    value.
    If public_only is True, requests with an identity are not cached.
    """
    _timeout = timeout
    _prefix = key_prefix
    if callable(vary_by):
        vary_by = [vary_by]

    def decorator(function):
        @wraps(function)
        def inner(request, *args, **kwargs):
            if public_only:
                if request.user.is_authenticated:
                    return function(request, *args, **kwargs)

            prefix = [_prefix]

            if isinstance(vary_by, list):
                prefix.extend([vfunc(request, *args, **kwargs) for vfunc in vary_by])

            prefix = "".join(prefix)

            if isinstance(_timeout, str):
                timeout = getattr(Config.system, _timeout)
            else:
                timeout = _timeout

            return dj_cache_page(timeout=timeout, key_prefix=prefix)(function)(
                request, *args, **kwargs
            )

        return inner

    return decorator


cache_page_by_ap_json = partial(cache_page, vary_by=[vary_by_ap_json])
