# -*- coding: UTF-8 -*-
from __future__ import unicode_literals, absolute_import, print_function

from api.models import Person, UserInnerInfo
from api.tool.user_tool import get_user_extra_by_user_id, user_register_log_data
from api.tool.person import update_password

from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.db import IntegrityError, transaction
from django.db.models import Q
from django.conf import settings

from rpc.decorators import bind, bind_context
from rpc.tool.dict_mixin import to_dict
from rpc.exceptions import RPCPermissionDeniedException, RPCIntegrityError
from rpc.tool.error_code import CODES, gen
from rpc.tool.log_tool import info_logger, logging_exception

from ..models import (
    UserPerm, BackendGroup, BackendPermission, BackendUser, BackendModule,
)
from ..datatables import BackendUserDT
from ..utils import send_user_passwd_email, send_passwd_reset_email
from ..queries.account import PermDQ, GroupDQ


@bind_context('backend/login')
def backend_login(ctx, username, password):
    """
    后台用户登录，只允许user.is_staff=True的用户登录

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        username : Str
        password : Str
    }
    data Result = {
        success : Bool
        session : Str | None
    }
    {-# Straight End #-}
    """
    user = authenticate(username=username, password=password)
    if user and user.is_staff:
        user.backend = "django.contrib.auth.backends.ModelBackend"
        ctx.session.do_login(user)
        return {
            'success': True,
            'session': ctx.session.session_key,
        }
    return {
        'success': False,
        'session': None,
    }


@bind_context('backend/password_change')
def backend_password(ctx, password_info):
    """
    后台用户修改密码，只允许user.is_staff=True的用户修改密码

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        username : Str
        password_info : Str
    }
    data Result = {
        success : Bool
    }
    {-# Straight End #-}
    """
    user = authenticate(username=ctx.session.user.username, password=password_info.get('old_password'))
    if user and user.is_staff:
        update_password(user.person, password_info.get('new_password'))
        user.backend = "django.contrib.auth.backends.ModelBackend"
        ctx.session.do_login(user)
        return {
            'success': True,
            'session': ctx.session.session_key,
        }
    return {
        'success': False,
        'session': None,
    }


@bind_context('backend/logout', login_required=True)
def backend_logout(ctx):
    """
    后台退出登录

    {-# Straight Configuration Alpha Typedef #-}
    data Params = {}
    data Result = None
    {-# Straight End #-}
    """
    ctx.session.do_logout()
    return None


@bind_context('backend/session_info')
def check_session(ctx, perms=False, groups=False, mars=False):
    """
    后台退出登录

    """
    user = ctx.session.user
    if user.is_authenticated():
        ret = {
            'is_login': True,
            'user': UserPerm.user_info(user, ctx.session.session_key, perms, groups, mars),
        }
        return ret
    return {'is_login': False}


@bind_context('backend/user/perms', login_required=True)
def get_permissions(ctx, fields=None):
    """
    {-# Straight Configuration Alpha Typedef #-}
    data Params = {
        fields   : Array[Str] | Str | None | Void
    }
    data Result = {
        perms : Array[Str]
    }
    {-# Straight End #-}
    """
    user = ctx.session.user
    return {
        'perms':
            UserPerm.perms_code(user) if fields is None else
            UserPerm.required_perms_code(user, fields),
    }


@bind_context('backend/user/groups', login_required=True)
def get_groups(ctx):
    """
    {-# Straight Configuration Alpha Typedef #-}
    data Params = {}
    data Result = {
        groups : Array[Any]
    }
    {-# Straight End #-}
    """
    user = ctx.session.user
    return {
        'groups': UserPerm.groups(user),
    }


@bind_context('backend/users', login_required=True)
def users(ctx):
    """get all backend users
    """
    users = BackendUser.query()
    ret = [
        to_dict(user, fields=['id', 'username', 'email']) for user in users
    ]
    return ret


@bind_context('backend/user/choices')
def backenduser_choices(ctx, q='', page=1, num=30, initial=None):
    page = int(page)
    num = int(num)

    if initial is not None:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    else:
        qry = Q(id__contains=q) | Q(username__contains=q)

    qry &= Q(is_staff=True)
    exclude_qry = Q(groups__name=u'医生')
    query = User.objects.using(settings.SLAVE_DB_NAME).filter(qry).exclude(exclude_qry)
    total_count = 0
    if initial:
        objs = query
    else:
        start_pos = (page - 1) * num
        start_pos = start_pos if start_pos >= 0 else 0
        objs = query[start_pos: start_pos+num]
    results = [
        {
            'id': obj.id,
            'text': u'{}:{}'.format(obj.id, obj.username),
        } for obj in objs
    ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('backend/users/datatable', staff_required=True)
def users_datatable(ctx, req_data):
    q = Q(is_staff=True)
    dtobj = BackendUserDT(User, q)
    return dtobj.process(req_data, ['username', 'email', 'last_name'])


@bind_context('backend/user', login_required=True)
def user(ctx, user_id=None):
    """get user info by user_id

    :perm limit: `others` only self info.
    :param user_id: [None|integer]specify an user, if is None, return self info.
    """
    user = User.objects.get(id=user_id)
    user_dict = to_dict(user, fields=['id', 'username', 'email'])
    userextra = get_user_extra_by_user_id(user.id)
    user_dict['login_mars'] = userextra.login_mars
    user_dict['groups'] = [g.id for g in user.belong_groups.all()]
    user_dict['perms'] = UserPerm.perms_id(user)
    return user_dict


@bind_context('backend/user/edit', staff_required=True)
def user_edit(ctx, user_id=None, user_info=None):
    if user_info is None:
        return None
    act = 'create' if user_id is None else 'modify'
    email = user_info.get('email', '')
    username = user_info.get('username', email.split('@', 1)[0])
    phone = user_info.get('phone', '').strip()
    name = user_info.get('name', '').strip()
    # if domain != 'wanmeizhensuo.com':
    #     # raise email not allowed
    #     return gen(CODES.EMAIL_NOT_ALLOWED)
    if act == 'create':
        random_passwd = User.objects.make_random_password(6)
        with transaction.atomic():
            try:
                user = User.objects.create_user(username, email, random_passwd)
            except IntegrityError:
                raise RPCIntegrityError
            user.is_staff = True
            user.save()
            Person.get_or_create(user)
            UserInnerInfo.objects.get_or_create(user=user)
            ctx.logger.app(**user_register_log_data(user_id=user.id))
            try:
                send_user_passwd_email(email, username, random_passwd)
            except:
                info_logger.info(__import__('traceback').format_exc())
                return gen(CODES.EMAIL_INVALID)
    elif act == 'modify':
        user = User.objects.get(id=user_id)
        userinnerinfo, _ = UserInnerInfo.objects.get_or_create(user_id=user_id)
        user.email = email
        if name and phone:
            userinnerinfo.name = name
            userinnerinfo.phone = phone
        else:
            user.username = username
        try:
            user.save()
            userinnerinfo.save()
        except IntegrityError:
            raise RPCIntegrityError
    userextra = get_user_extra_by_user_id(user.id)
    userextra.login_mars = user_info.get('login_mars', False)
    userextra.save()

    group_ids = user_info.get('groups', None)
    if group_ids is not None:
        new_groups = set(BackendGroup.objects.filter(id__in=group_ids))
        old_groups = set(user.belong_groups.all())
        user.belong_groups.add(*(new_groups - old_groups))
        user.belong_groups.remove(*(old_groups - new_groups))
    return


@bind_context('backend/user/passwd_reset', staff_required=True)
def user_passwd_reset(ctx, user_id):
    user = User.objects.get(id=user_id)
    new_passwd = User.objects.make_random_password(6)
    with transaction.atomic():
        update_password(user.person, new_passwd)
        send_passwd_reset_email(user.email, new_passwd)
    return gen(CODES.SUCCESS)


@bind_context('backend/groups', staff_required=True)
def groups(ctx):
    """get backend groups.
    :perm limit: superuser can get all groups
                 others can get groups they belonged to.
    """
    user = ctx.session.user
    if user.is_superuser:
        groups = BackendGroup.objects.all()
    else:
        groups = BackendGroup.objects.filter(members=user)
    return [group.to_dict(
                expands={'leader': ['id', 'username']},
            ) for group in groups]


@bind_context('backend/group', staff_required=True)
def group(ctx, group_id, info_options=None):
    """get group info by group_id.

    :param group_id: [integer]specify a group.
    """
    user = ctx.session.user
    try:
        group = BackendGroup.objects.get(id=group_id)
    except:
        return None
    if not user.is_superuser and user not in group.members.all():
        raise RPCPermissionDeniedException
    if info_options is None:
        info_options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    return group.to_dict(**info_options)


@bind_context('backend/group/edit', staff_required=True)
def group_edit(ctx, group_id=None, group_info=None):
    """create of modify group info.

    :param group_id: [None|int]specify a group, if is None, create it.
    :param group_info: [None|dict]info to be created or modified.
    """
    if group_info is None:
        return None
    act = 'create' if group_id is None else 'modify'
    if act == 'create':
        try:
            group = BackendGroup.objects.create(
                    name=group_info['name'],
                    title=group_info['title'])
        except IntegrityError:
            raise RPCIntegrityError
    elif act == 'modify':
        group = BackendGroup.objects.get(id=group_id)
        title = group_info.get('title', '')
        if title:
            group.title = title

    if 'leader' in group_info:
        try:
            leader = User.objects.get(id=group_info['leader'])
            group.set_leader(leader)
        except:
            pass

    group.save()

    mem_ids = group_info.get('members', None)
    if mem_ids is not None:
        if group.leader:
            mem_ids.append(group.leader.id)
        new_members = set(User.objects.filter(id__in=mem_ids))
        old_members = set(group.members.all())
        group.members.add(*(new_members - old_members))
        group.members.remove(*(old_members - new_members))

    perm_ids = group_info.get('permissions', None)
    if perm_ids:
        new_perms = set(BackendPermission.objects.filter(id__in=perm_ids))
        old_perms = set(group.permissions.all())
        group.permissions.add(*(new_perms - old_perms))
        group.permissions.remove(*(old_perms - new_perms))

    return


@bind_context('backend/perms', staff_required=True)
def perms(ctx):
    perms = BackendPermission.objects.all()
    return [
        perm.to_dict(expands={'module': {'id', 'name', 'title'}}) for perm in perms
    ]


@bind_context('backend/perm/edit', staff_required=True)
def perm_edit(ctx, perm_id=None, perm_info=None):
    if perm_info is None:
        return None
    if perm_id is None:
        # create
        perm = BackendPermission.objects.create(
            name=perm_info['name'],
            title=perm_info['title'],
            module_id=perm_info['module'],
        )
    else:
        perm = BackendPermission.objects.get(id=perm_id)
        perm.name = perm_info.get('name', perm.name)
        perm.title = perm_info.get('title', perm.title)
        perm.module_id = perm_info.get('module', perm.module_id)
        perm.save()
    return True


@bind_context('backend/modules', staff_required=True)
def modules(ctx):
    modules = BackendModule.objects.all()
    return [module.to_dict() for module in modules]


@bind_context('backend/module/get', staff_required=True)
def module_get(ctx, module_name):
    try:
        module = BackendModule.objects.get(name=module_name)
    except BackendModule.DoesNotExist:
        return None

    return to_dict(module)


@bind_context('backend/module/edit', staff_required=True)
def module_edit(ctx, module_id=None, module_info=None):
    if module_info is None:
        return None
    if module_id is None:
        # create
        module = BackendModule.objects.create(
            name=module_info['name'],
            title=module_info['title'],
        )
        module.init_perms()
    else:
        module = BackendModule.objects.get(id=module_id)
        module.name = module_info.get('name', module.name)
        module.title = module_info.get('title', module.title)
        module.save()
    return True


@bind('backend/module/add_multi')
def module_add_multi(module_info):
    names = [name for name, title in module_info.iteritems()]
    modules = BackendModule.objects.filter(name__in=names)
    old_names = [m.name for m in modules]
    add_names = set(names) - set(old_names)
    for name in add_names:
        try:
            with transaction.atomic():
                module = BackendModule.objects.create(
                    name=name, title=module_info[name],
                )
                module.init_perms()
        except:
            logging_exception()


@bind('backend/perm/add_multi')
def perm_add_multi(perm_info):
    for k, v in perm_info.iteritems():
        try:
            module_name, perm_name = k.split('.')
            title = v
            modules = BackendModule.objects.filter(name=module_name)
            if not modules.exists():
                continue
            perm, _ = BackendPermission.objects.get_or_create(
                name=perm_name,
                module=modules.first(),
                defaults={'title': title},
            )
        except:
            logging_exception()


@bind_context('backend/user/set_active', staff_required=True)
def staff_set_active(ctx, id, is_active):
    user = User.objects.get(id=id)
    user.is_staff = is_active
    user.save()
    return {
        'success': True,
        'id': user.id,
        'is_active': user.is_staff,
    }


@bind_context('hera/perm/query')
def perm_query(ctx, options):
    dqobj = PermDQ()
    return dqobj.process(**options)


@bind_context('hera/group/query')
def group_query(ctx, options):
    dqobj = GroupDQ()
    return dqobj.process(**options)


@bind_context('backend/is_staff')
def is_staff(ctx, user_id):
    user = User.objects.get(id=user_id)
    return {
        'is_staff': user.is_staff,
    }
