import threading
from typing import Any, Callable, TYPE_CHECKING

from django.conf import settings

from .exceptions import RequestContextProxyError, RequestContextError
from console_base.typehints import LCHttpRequest

if TYPE_CHECKING:
    from console_base.models import BaseUUIDPKModel


class RequestContext(object):
    def __init__(self) -> None:
        self.__request_context = threading.local()

    def get_attr(self, key: Any, default: Any = None) -> Any:
        return getattr(self.__request_context, key, default)

    def set_attr(self, key: Any, value: Any) -> None:
        try:
            getattr(self.__request_context, key)
        except AttributeError:
            return setattr(self.__request_context, key, value)

        # In development, return so that the failing test exception
        # bubbles up to the top, rather than the RequestContextError
        if settings.DEVELOPMENT:
            return

        raise RequestContextError.ObjExisted(f"{key} already exists, can't replace")

    def clear(self) -> None:
        return self.__request_context.__dict__.clear()

    def init_by_request(self, request: LCHttpRequest) -> None:
        self.set_attr('request', request)
        self.set_attr('g', GObject())


class GObject(object):
    def get(self, name: str, default: Any = None) -> Any:
        return self.__dict__.get(name, default)

    def pop(self, name: str, default: Any = None) -> Any:
        return self.__dict__.pop(name, default)

    def setdefault(self, name: str, default: Any = None) -> Any:
        return self.__dict__.setdefault(name, default)

    def __contains__(self, item: Any) -> bool:
        return item in self.__dict__

    def __iter__(self) -> Any:
        return iter(self.__dict__)


class RequestContextProxy(object):
    def __init__(self, wrapped_func: Callable, read_only: bool = False) -> None:
        if not callable(wrapped_func):
            raise RequestContextProxyError.WrongWrappedFunc('wrapped_func must be callable')
        object.__setattr__(self, '_RequestContextProxy__wrapped_func', wrapped_func)
        object.__setattr__(self, 'read_only', read_only)

    def _get_obj(self) -> 'BaseUUIDPKModel':
        obj = self.__wrapped_func()
        if obj is None:
            raise RequestContextProxyError.ObjNotFound('wrapped_func return nothing')
        return obj

    @property
    def __dict__(self) -> dict:  # type: ignore[override]
        try:
            return self._get_obj().__dict__
        except RequestContextProxyError.ObjNotFound:
            raise AttributeError('__dict__') from None

    def __repr__(self) -> str:
        return repr(self._get_obj())

    def __str__(self) -> str:
        return str(self._get_obj())

    def __dir__(self):
        return dir(self._get_obj())

    def __getattr__(self, name: str) -> Any:
        return getattr(self._get_obj(), name)

    def __setattr__(self, key: Any, value: Any) -> None:
        if self.read_only:
            raise RequestContextProxyError.ObjReadOnly("you can't change a readonly obj")
        return setattr(self._get_obj(), key, value)

    def __delattr__(self, item: Any) -> None:
        return delattr(self._get_obj(), item)
