# -*- coding: UTF-8 -*-
import functools
from django.conf import settings
from gm_rpcd.all import context, bind
from gm_rpcd.internals.dynamic_scopes import DynamicVariableUnboundError
from helios.rpc import create_default_invoker
from utils.rpc import logging_exception, gen
from gm_types.error import ERROR as CODES

rpc_invoker = create_default_invoker(debug=settings.DEBUG)


def get_current_rpc_invoker():
    try:
        return context.rpc
    except DynamicVariableUnboundError:
        return rpc_invoker


class RPCMixin(object):
    @classmethod
    def call_rpc(cls, api_endpoint, **kwargs):
        """call rpc.

        get rpc invoker from current ctx, this method only support call rpc sequentially.
        """
        r = get_current_rpc_invoker()
        result = r[api_endpoint](**kwargs)
        return result.unwrap()


def get_objects_from_queryset_and_pk_list(queryset, pk_list):
    qs_objects = queryset.filter(pk__in=pk_list)
    obj_map = {o.pk: o for o in qs_objects}
    return [
        obj_map[pk]
        for pk in pk_list
        if pk in obj_map
        ]


def bind_context(endpoint, **kwargs):
    def _wrap(func):
        @functools.wraps(func)
        def _wrapper(*args, **kwargs):
            return func(context, *args, **kwargs)

        return bind(endpoint)(_wrapper)

    return _wrap


def to_dict(obj, fields=None, excludes=None, expands=None):
    ret = {}
    model_fields = []
    for field in obj._meta.get_fields():
        field_name = field.name
        model_fields.append(field_name)
        if (excludes is not None and field_name in excludes) or \
                (fields is not None and field_name not in fields):
            # skip field that in the excludes or not in ther fields
            continue
        try:
            fval = getattr(obj, field_name, None)
        except:
            fval = None
        if not isinstance(field, models.fields.related.RelatedField) or not fval:
            # Basic Field, get value directly
            ret[field_name] = _get_val(fval)
        else:
            if expands is None or field_name not in expands.keys():
                if isinstance(field, models.ForeignKey):
                    ret[field_name] = fval.id if fval else None
                else:
                    # ManyToManyField
                    ret[field_name] = list(fval.values_list('id', flat=True))
            else:
                # need expand related object
                related_fields = expands[field_name]
                if isinstance(field, models.ForeignKey):
                    ret[field_name] = _fetch_related(fval, related_fields)
                else:
                    # ManyToManyField
                    ret[field_name] = [
                        _fetch_related(o, related_fields) for o in fval.all()
                    ]
    # access the field not a Model's Field
    if fields:
        for field_name in set(fields) - set(model_fields):
            field_list = field_name.split('__')
            if len(field_list) > 1:
                fval = obj
                for i in field_list:
                    if fval is None:
                        break
                    try:
                        fval = getattr(fval, i, None)
                    except:
                        break
            else:
                fval = get_non_model_field_value(obj, field_name)
            ret[field_name] = _get_val(fval)
    return ret