import inspect
import json
from functools import wraps
from typing import Annotated, Any, Callable, ParamSpec, TypeVar

from fastapi import Depends, Header, Request, Response
from pydantic import BaseModel
from starlette.templating import _TemplateResponse

from rvpc.templates import TEMPLATES


def hx_redirect(path: str, target: str = "main") -> Response:
    response = Response(status_code=301)
    response.headers["HX-Location"] = json.dumps(
        {"path": path, "target": target}
    )

    return response


HXRequest = Annotated[str | None, Header()]


def check_hx(hx_request: HXRequest) -> bool:
    return True if hx_request else False


HXStatus = Annotated[bool, Depends(check_hx)]


class RefreshResponse(_TemplateResponse):
    pass


class HXResponse(_TemplateResponse):
    pass


def page_refresh(request: Request) -> _TemplateResponse:
    response = TEMPLATES.TemplateResponse(
        request=request,
        name="root.html",
        context={
            "url": request.scope["root_path"] + request.scope["route"].path,
            "vals": json.dumps(request.query_params._dict),
        },
    )
    return response


P = ParamSpec("P")
T = TypeVar("T")

_default = object()


def append_to_signature(
    func: Callable[P, T],
    *params: inspect.Parameter,
    return_annotation: Any = _default,
) -> Callable[P, T]:
    """
    Appends the given parameters to the *end* of the signature of the given function.

    Notes:
        - This method does not change the function's arguments, it only makes FastAPI's
        dependency resolution system recognize inserted parameters.
        - This is *not* a general purpose method, it is strongly recommended to only
        append keyword-only parameters that have "unique" names that are unlikely to
        be already in the function's signature.

    Arguments:
        func: The function whose signature should be extended.
        params: The parameters to add to the function's signature.
        return_annotation: If set, the return annotation of `func` will be replaced
            with the given value.

    Returns:
        The received function with an extended `__signature__`.
    """
    signature = inspect.signature(func, eval_str=True)
    func.__signature__ = signature.replace(  # type: ignore[attr-defined]
        parameters=(*signature.parameters.values(), *params),
        return_annotation=signature.return_annotation
        if return_annotation is _default
        else return_annotation,
    )
    return func


def get_request(request: Request):
    return request


ReqDep = Annotated[Request, Depends(get_request)]


def smart(template: str):
    def inner(func):
        @wraps(func)
        async def handle_refresh(
            _request: Request,
            *args,
            **kwargs,
        ):
            context = await func(*args, **kwargs)

            if context is None:
                context = {}

            if isinstance(context, BaseModel):
                context = context.model_dump()

            if _request.headers.get("HX-Request"):
                if isinstance(context, Response):
                    return context
                return TEMPLATES.TemplateResponse(
                    request=_request, name=template, context=context
                )
            else:
                return page_refresh(request=_request)

        return append_to_signature(
            handle_refresh,  # type: ignore[arg-type]
            inspect.Parameter(
                "_request",
                inspect.Parameter.KEYWORD_ONLY,
                annotation=ReqDep,
            ),
        )

    return inner
