from __future__ import annotations

from contextvars import ContextVar, Token
from types import SimpleNamespace
from typing import Any, Callable

_request_var: ContextVar[Any] = ContextVar("exile_request")
_current_app_var: ContextVar[Any] = ContextVar("exile_current_app")
_g_var: ContextVar[SimpleNamespace] = ContextVar("exile_g")


class LocalProxy:
    __slots__ = ("_getter", "_name")

    def __init__(self, getter: Callable[[], Any], name: str) -> None:
        object.__setattr__(self, "_getter", getter)
        object.__setattr__(self, "_name", name)

    def _get_current_object(self) -> Any:
        return object.__getattribute__(self, "_getter")()

    def __getattr__(self, item: str) -> Any:
        return getattr(self._get_current_object(), item)

    def __setattr__(self, key: str, value: Any) -> None:
        setattr(self._get_current_object(), key, value)

    def __delattr__(self, item: str) -> None:
        delattr(self._get_current_object(), item)

    def __repr__(self) -> str:
        try:
            value = self._get_current_object()
        except RuntimeError:
            return f"<LocalProxy {self._name} unbound>"
        return repr(value)

    def __bool__(self) -> bool:
        return bool(self._get_current_object())


def _runtime_error(name: str) -> RuntimeError:
    return RuntimeError(f"Working outside of {name} context.")


def _lookup_request() -> Any:
    try:
        return _request_var.get()
    except LookupError as exc:
        raise _runtime_error("request") from exc


def _lookup_current_app() -> Any:
    try:
        return _current_app_var.get()
    except LookupError as exc:
        raise _runtime_error("app") from exc


def _lookup_g() -> SimpleNamespace:
    try:
        return _g_var.get()
    except LookupError as exc:
        raise _runtime_error("request") from exc


request = LocalProxy(_lookup_request, "request")
current_app = LocalProxy(_lookup_current_app, "current_app")
g = LocalProxy(_lookup_g, "g")


def push_request_context(app: Any, req: Any) -> tuple[Token[Any], Token[Any], Token[SimpleNamespace]]:
    app_token = _current_app_var.set(app)
    request_token = _request_var.set(req)
    g_token = _g_var.set(SimpleNamespace())
    return app_token, request_token, g_token


def pop_request_context(tokens: tuple[Token[Any], Token[Any], Token[SimpleNamespace]]) -> None:
    app_token, request_token, g_token = tokens
    _g_var.reset(g_token)
    _request_var.reset(request_token)
    _current_app_var.reset(app_token)

