import contextlib
import threading

from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from cached_property import cached_property
from helios.rpc import create_default_invoker
from . import auth
from .exceptions import RPCLoginRequiredException
from .tool.log_tool import logging_exception, info_logger


class Session(object):
    def __init__(self, session_key=None):

        assert session_key is None or isinstance(session_key, basestring)
        if session_key == '':  # make empty session_key as empty session
            session_key = None
        django_session = auth.get_django_session(session_key)
        django_user = auth.get_user_from_django_session(django_session)
        self._django_session = django_session
        self._django_user = django_user

    def do_login(self, user):
        auth.login(self._django_session, user)
        self._django_user = user
        self._django_session.save()

    def do_logout(self):
        auth.logout(self._django_session, self._django_user)
        self._django_user = auth.AnonymousUser()
        self._django_session.save()

    @property
    def session_key(self):
        return self._django_session.session_key

    @property
    def has_login(self):

        user = self.user
        info_logger.info(user)
        res = user.is_authenticated()
        return res
    def login_required(self):
        if not self.has_login:
            raise RPCLoginRequiredException

    def set_wechat_unionid(self, unionid):
        if unionid:
            sk = "wx_unionid"
            self._django_session[sk] = unionid
            self._django_session.save()

    def get_wechat_unionid(self):
        sk = "wx_unionid"
        result = self._django_session.get(sk, None)
        return result

    def set_wechat_openid(self, wechat_appid, openid):
        if wechat_appid and openid:
            sk = "wx_openid_for_app_{}".format(wechat_appid)
            self._django_session[sk] = openid
            self._django_session.save()

    def get_wechat_openid(self, wechat_appid):
        result = None
        if wechat_appid:
            sk = "wx_openid_for_app_{}".format(wechat_appid)
            result = self._django_session.get(sk, None)
        return result

    @property
    def user_id(self):
        return self.user.id

    @property
    def user(self):
        user = self._django_user
        return user if user.is_active else AnonymousUser()

    @property
    def groups(self):
        return self.user.belong_groups.values_list('name', flat=True)


_base_invoker = create_default_invoker(
    debug=settings.DEBUG
).with_config(
    dump_curl=True
)


class NoCurrentContextError(Exception):
    pass


def get_current_context_or_throw_exception():
    context = ContextManager.get_active_context()
    if context:
        return context
    raise NoCurrentContextError


def _do_get_rpc_remote_invoker():
    context = ContextManager.get_active_context()
    if context:
        return context.rpc_remote
    else:
        return _base_invoker


def get_rpc_remote_invoker():
    return _do_get_rpc_remote_invoker()


def get_gaia_local_invoker():
    context = ContextManager.get_active_context()
    if context:
        return context.gaia_local
    else:
        from .nested_invoker import NestedInvoker
        return NestedInvoker(ctx=context)


class Context(object):
    has_session = None
    logger = None

    def __init__(self, session_key, request=None):
        self.__session_key = session_key
        self.has_session = bool(session_key)
        self._request = request

    @cached_property
    def session(self):
        return Session(session_key=self.__session_key)

    @cached_property
    def gaia_local(self):
        from .nested_invoker import NestedInvoker
        return NestedInvoker(self)

    @property
    def rpc(self):
        try:
            raise Exception(u'should not use Context.rpc, use Context.gaia_local or Context.rpc_remote')
        except Exception:
            if settings.DEBUG:
                raise
            else:
                logging_exception()
        return self.gaia_local

    @cached_property
    def rpc_remote(self):
        if self._request:
            client_info = self._request.client_info
        else:
            client_info = None
        return _base_invoker.with_config(
            session_key=self.__session_key,
            client_info=client_info,
        )


class ContextManager(object):
    _active_context_local = threading.local()

    @classmethod
    @contextlib.contextmanager
    def with_active_context(cls, context):
        """
        :type context: Context
        """
        acl = cls._active_context_local
        previous = getattr(acl, 'context', None)
        acl.context = context
        try:
            yield
        finally:
            acl.context = previous

    @classmethod
    def get_active_context(cls):
        """
        :rtype: Context | None
        """
        return getattr(cls._active_context_local, 'context', None)


class ConnectionInfo(object):
    request = None
    client_ip = None

    def __init__(self, request, client_ip=None):
        self.request = request

        # Xing Ye tells me that there are these settings on proxy:
        #    proxy_set_header X-Real-IP $remote_addr;
        #    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        if not client_ip:
            try:
                client_ip = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
            except Exception:
                pass
        self.client_ip = client_ip


class Request(object):
    method = None
    params = None
    session_key = None
    environment = None
    is_nested_call = None

    context = None
    method_info = None

    request_info = None

    def __init__(self, method, params, session_key, environment, is_nested_call=False):
        self.method = method
        self.params = params
        self.session_key = session_key
        self.environment = environment
        self.is_nested_call = is_nested_call

        self.context = Context(session_key=session_key, request=self)

    @property
    def client_info(self):
        # return (self.environment or {}).get(u'client_info')
        request_info = self.request_info
        if request_info:
            return request_info.get_client_info()
        return None


def create_fake_context():
    return Context(session_key=None)
