# coding=utf-8
from __future__ import unicode_literals, absolute_import, print_function

import datetime
import re
import json
from hashlib import md5

from django.conf import settings
from django.db.models import Q
from gm_types.doctor import USER_BROWSING_TYPE

from gm_types.gaia import POINTS_TYPE, WRONG_DEVICE_ID, ORDER_STATUS, USER_RIGHTS_LEVEL, LOGIN_AUTH_TYPE, USER_CLASSIFY
from gm_types.gaia import CLIENT_TYPE
from gm_types.push import PUSH_INFO_TYPE, PERSONAL_PUSH_TYPE, AUTOMATED_PUSH
from gm_types.gaia import PLATFORM_CHOICES
from gm_types.user_hierarchy import EventType
import point
from api.models import City, DeviceBinding, MerchantRelevance, ConversationUserStatus, ServiceItemKey, Conversation
from api.models import DOCTOR_TYPE, VERIFY_CODE_TYPE
from api.models import Doctor
from api.models import MEMBERSHIP_LEVEL
from api.models import Person
from api.models import User, UserAuthentication
from api.models import UserBlackList
from api.models import UserExtra, Order, UserExtraToCollege, PlatformUser
from api.models import College, ProtectPhone
from api.models.types import USER_ORDER_TYPE
from api.models.others import UserCheckinLog
from api.tasks.push_task import allocate_push_task_one, allocate_push_task_users
from api.trigger.user_trigger import user_change_trigger
from api.tool.answer_tool import get_user_questions_count
from api.tool.datetime_tool import get_timestamp_or_none
from api.tool.image_utils import get_full_path
from api.tool.live_tool import get_person_live_count
from api.tool.log_tool import logging_exception
from api.tool.notification_tool import send_notification
from api.tool.user_tool import (
    get_user_from_context, get_doctor_by_user_id, get_portrait_by_user_id,
    get_username_with_title, filter_user_nick_name, get_user_extra_by_user_id,
    update_user_info_by_user_id, get_membership_level_by_user_id,
    user_register_log_data, get_user_type_id, get_user_by_id, get_user_by_doctor_id,
    get_user_level, user_have_payment, in_new_user_whitelist
)
from api.tool.verification_code import calc_verification_code, VerifiedCode
from hippo.models import MerchantPvStat
from hippo.models.user_list import UserList

from relation.models import UserTagRelation
from statistic.models import Device, DeviceIDFA
from mimas.tasks import send_event_task

from rpc.decorators import bind, cache_page
from rpc.decorators import bind_context, list_interface
from rpc.tool.dict_mixin import to_dict
from rpc.tool.error_code import gen, CODES
from rpc.tool.protocol import gm_protocol

from search.utils.diary import filter_diary
from search.utils.user import filter_user
from social.models import SocialInfo

from talos.models.live import ShortvideoBlackList
from talos.tools.push_tool import limit_push
from utils.phone import format_phone
from api.tool.user_tool import is_new_user_local
from api.tool.user_tool import get_user_extra_by_user_id, is_tongdun_user
from api.tool.sms_tool import is_plus_phone_num
from statistic.models import ClassifyUser
from api.util.user_util import get_phone_by_user_ids


@bind_context('api/user/get_rating_alert_info', login_required=True)
def user_get_rating_alert_info(ctx):
    user = get_user_from_context(ctx)
    user_extra = get_user_extra_by_user_id(user.id)
    return {
        'rating_click': user_extra.rating_click,
        'last_alert_rating_time': get_timestamp_or_none(user_extra.last_alert_rating_time),
    }


@bind_context("api/user_info", login_required=True)
@bind_context("api/user/info", login_required=True)
def get_user_info(ctx):
    """
    展示用户信息
    """
    user = get_user_from_context(ctx)
    Person.get_or_create(user)
    user_extra = get_user_extra_by_user_id(user.id)
    doctor = get_doctor_by_user_id(user.id)

    result = {
        'user_id': user.id,
        'person_id': user.person.id.hex,
        'real_name': user_extra.name or u'',
        'nick_name': filter_user_nick_name(user) or u'',
        'portrait': get_full_path(user_extra.portrait) or get_full_path(u'img%2Fuser_portrait.png'),
        'phone': user.person.phone if user.person.phone and is_plus_phone_num(user.person.phone) else u'',
        'account_phone': user.person.phone,
        'city': user_extra.city.name if user_extra.city else u'',
        'city_id': user_extra.city.id if user_extra.city else u'',
        'address': user_extra.address or u'',
        'points': point.get_point_for(user=user),
        'cover': get_full_path(user_extra.cover) or settings.DEFAULT_USER_PROFILE_COVER,
        'membership_level': user_extra and user_extra.membership_level or MEMBERSHIP_LEVEL.NORMAL,
        'rights_level': user_extra and user_extra.user_rights_level or USER_RIGHTS_LEVEL.V1,
        'doctor_id': None,
        'doctor_name': None,
        'zip_code': user_extra.zip_code,
        'referral_code': user_extra.referral_code,
        'rating_click': user_extra.rating_click,
        'last_alert_rating_time': get_timestamp_or_none(user_extra.last_alert_rating_time),
        'email': user.person.email,
        'area': '',
        'phone_prefix': '',
        'birthday': user_extra.birthday_str,
        'has_password': False if not user.person.password else True,
    }

    try:
        area_info = user.person.area
        result['area'] = area_info.area_name
        result['phone_prefix'] = area_info.phone_prefix
    except:
        pass

    if doctor:
        result.update({
            'doctor_id': doctor.id,
            'doctor_name': doctor.name,
            'hospital_id': doctor.hospital.id if doctor.hospital else '',
            'hospital_name': doctor.hospital.name if doctor.hospital else '',
        })

    return result


@bind_context("api/update_user_info", login_required=True)
@bind_context("api/user/update", login_required=True)
def update_user_info(ctx, real_name=None, nick_name=None, phone=None, address=None, birthday=None, zip_code=None):
    """
    修改用户信息
    """
    user = get_user_from_context(ctx)
    if not user:
        return

    result = update_user_info_by_user_id(user.id, real_name, nick_name, phone, address, birthday, zip_code)
    user_change_trigger(user.id)
    return result


@bind_context("api/user/diary_info")
def api_user_diary_info(ctx, user_id=None):
    """
    获取用户日记数量
    """
    if not user_id:
        user = get_user_from_context(ctx)
        user_id = user.id if user else None

    if not user_id:
        return {
            'diary_num': 0
        }

    filters = {
        'user_id': user_id,
    }
    try:
        diary_num = filter_diary(offset=0, size=1,
                                 filters=filters, expose_total=True)['total']
    except:
        logging_exception()
        diary_num = 0
    return {
        'diary_num': diary_num
    }


@bind_context("api/user/user_info")
def api_user_user_info(ctx, user_id, use_new_level_icon=False):
    """
    展示用户信息(个人主页中用户基本信息部分)
    changelog:
        5.9.5 增加person_id 用于客服中通过user_id获取对方person的信息
        7.0.0 增加face_detected 用户是否建立过人脸识别档案
        7.10.0 删除face_detected 用户是否建立过人脸识别档案, 通过gm-face获取信息
        7.1.5 增加用户所在城市user_location及被点赞数vote_count，直播数live_count,发布问题数ask_count

        updated at 2017-11-09, check if user is active
    """
    try:
        user = User.objects.get(pk=user_id, is_active=True)
    except (User.DoesNotExist, ValueError):
        return gen(CODES.USER_NOT_FOUND)

    social_info = SocialInfo(uid=user_id)
    current_user = get_user_from_context(ctx)
    referral, created = UserExtra.objects.get_or_create(user=user)
    if current_user:
        current_social_info = SocialInfo(uid=current_user.id)

    filters = {
        'user_id': user_id,
    }
    try:
        diary_num = filter_diary(offset=0, size=1,
                                 filters=filters, expose_total=True)['total']
    except:
        logging_exception()
        diary_num = 0

    topic_num = int(user.person.topic_count)

    user_level = get_user_level(user, use_new_level_icon)
    return {
        'user_id': user.id,
        'person_id': user.person.id.hex,
        'nick_name': filter_user_nick_name(user) or u'',
        'portrait': get_full_path(user.userextra.portrait) or get_full_path('img%2Fuser_portrait.png'),
        'cover': get_full_path(user.userextra.cover) or settings.DEFAULT_USER_PROFILE_COVER,
        'following_count': social_info.following_count,
        'fans_count': social_info.fans_count,
        'is_following': current_social_info.is_following_user(uid=user_id) if current_user else False,
        'follow_status': current_social_info.follow_status(uid=user_id) if current_user else False,
        'membership_level': user.userextra and user.userextra.membership_level or MEMBERSHIP_LEVEL.NORMAL,
        'referral_code': referral.referral_code,
        'is_doctor': referral.is_doctor,
        'doctor_id': referral.related_doctor_id,
        'doctor_type': referral.related_doctor_type,
        # TODO
        'bg_image': get_full_path(user.userextra.portrait) or get_full_path('img%2Fuser_portrait.png'),
        'diary_num': diary_num,
        'topic_num': topic_num,

        # added 7.1.5
        'user_location': user.userextra.city and user.userextra.city.name or u'中国',
        'vote_count': user.person.vote_count,
        'live_count': get_person_live_count(user.id),
        'ask_count': get_user_questions_count(user.id),

        # added 7.2.0
        'birthday': user.userextra and user.userextra.birthday_str or '',
        'rights_level': user.userextra and user.userextra.user_rights_level or USER_RIGHTS_LEVEL.V1,
        'membership_icon': user_level['membership_icon'],
        'level_icon': user_level['level_icon'],
        'constellation_icon': user_level['constellation_icon'],

        # added at 7.4.5
        'current_city_id': user.userextra.current_city_id,
        'phone': user.userextra.phone or u'',
        'points': point.get_point_for(user=user),
    }


@bind_context("api/user/update_user_info", login_required=True)
def update_user_cover_portrait(ctx, user_info):
    """
    修改用户信息(头像、封面)
    :param ctx:
    :param user_info:{"cover":"2014/11/10/37d9cb8bd6", "portrait":"2014/11/05/838b1395e3"}
    :return:
    """
    user = get_user_from_context(ctx)
    userextra = UserExtra.objects.filter(user=user)
    if userextra:
        try:
            userextra.update(**user_info)
            user_change_trigger(user_id=user.id)
            growth_value, point_value, submit_count = 0, 0, 0
            if user_info.get('portrait', ''):
                event_data = send_event_task(user_id=user.id, event_type=EventType.COMPLETEPORTRAIT)
                growth_value, point_value = event_data['growth_value'], event_data['point_value']
                submit_count = event_data['submit_count']
            return {'msg': u'修改成功', 'growth_value': growth_value, 'point_value': point_value,
                    'submit_count': submit_count}
        except:
            return gen(CODES.UPDATE_FAIL)
    else:
        return gen(CODES.MESSAGE_INVALID_USER)


@bind_context('api/user/set_channel')
def user_set_channel(ctx, channel, user_id=None):
    if not isinstance(channel, (unicode, str)) or channel == 'null':
        return {'updated': False}

    if user_id is None:
        user = get_user_from_context(ctx)
    else:
        user = User.objects.get(pk=user_id)

    if user.userextra and user.userextra.channel:
        _channel = user.userextra.channel.strip()
        _channel = channel.lower()
        if _channel and _channel != 'null':
            return {'updated': False}

    channel = channel.strip().lower()
    user.userextra.channel = channel
    user.userextra.save()
    user_change_trigger(user_id=user.id)

    return {'updated': True}


@bind('api/user/user_doctor_info')
def api_user_doctor_info(user_id):
    """
    展示用户信息，若用户是医生则附加医生信息
    :param user_id:
    :return:
    """
    try:
        user_obj = User.objects.select_related('person').get(pk=user_id)
    except User.DoesNotExist:
        return gen(CODES.USER_NOT_FOUND)

    doctor = get_doctor_by_user_id(user_id)
    if doctor:
        if doctor.doctor_type == DOCTOR_TYPE.DOCTOR:
            portrait = get_full_path(doctor.portrait or settings.DOCTOR_DEFAULT_PORTRAIT_KEY)
        else:
            portrait = get_full_path(doctor.portrait or settings.HOSPITAL_DEFAULT_PORTRAIT_KEY)
        return {
            'user_id': user_id,
            'doctor_type': doctor.doctor_type,
            'doctor_title': doctor.title,  # 医生职称
            'doctor_id': doctor.doctor_type == DOCTOR_TYPE.DOCTOR and doctor.id or None,
            'hospital_id': doctor.doctor_type == DOCTOR_TYPE.OFFICER and getattr(doctor.hospital, 'id', None) or None,
            'nickname': doctor.name,
            'portrait': portrait,
            'person_id': user_obj.person.id.hex,
            'hospital_name': doctor.hospital.name if doctor.hospital else ''
        }
    else:
        try:
            user = User.objects.select_related('userextra').get(pk=user_id)
            return {
                'user_id': user.id,
                'doctor_id': None,
                'hospital_id': None,
                'doctor_title': None,
                'nickname': filter_user_nick_name(user),
                'portrait': get_full_path(user.userextra.portrait) or get_full_path(u'img%2Fuser_portrait.png'),
                'person_id': user_obj.person.id.hex,
            }
        except User.DoesNotExist:
            return gen(CODES.MESSAGE_INVALID_USER)


@bind('api/user/get_doctor_id')
def get_doctor_id(user_id):
    """通过userid 给私信提供是否是医生的接口"""
    doctor = get_doctor_by_user_id(user_id)
    if not doctor:
        return None
    return doctor.id


@bind('api/user/doctor_infos')
def user_doctor_infos(user_ids=[]):
    """
        给私信列表提供查询是否是医生的接口
    """
    user_map = {user.id: user for user in User.objects.filter(pk__in=user_ids)}
    doctor_map = {doctor.user_id: doctor for doctor in Doctor.objects.filter(user_id__in=user_ids)}
    user_infos = []
    for uid in user_ids:
        item = None
        if uid in doctor_map:
            doctor = doctor_map[uid]
            item = {
                'id': doctor.id,
                'user_id': uid,
                'doctor_id': doctor.doctor_type == DOCTOR_TYPE.DOCTOR and doctor.id or None,
                'nickname': doctor.name,
                'portrait': get_full_path(doctor.portrait),
            }
        elif uid in user_map:
            user = user_map[uid]
            item = {
                'id': None,
                'user_id': user.id,
                'nickname': user.last_name,
                'portrait': get_full_path(user.userextra.portrait) or get_full_path(u'img%2fuser_portrait.png'),
                'doctor_id': None,
            }
        user_infos.append(item)
    return user_infos


@bind_context("api/user/following")
@list_interface(offset_name='start_num', limit_name='count', element_model=User)
def user_following_list(ctx, user_id, count=10, start_num=0):
    """
    关注列表
    :param user_id:
    :param count:
    :param start_num:
    :return:
    """
    start_num = start_num >= 0 and start_num or 0

    result = []
    try:
        user = User.objects.get(pk=user_id)
        social_info = SocialInfo(uid=user.id)

        current_user = get_user_from_context(ctx)
        if current_user:
            current_social_info = SocialInfo(uid=current_user.id)
        uids = social_info.following(start_num, count)
        for uid in uids:
            uid = int(uid)
            try:
                u = User.objects.get(pk=uid)
            except User.DoesNotExist:
                continue

            topic_num = int(u.person.topic_count)
            vote_num = int(u.person.vote_count)
            result.append({
                'portrait': get_portrait_by_user_id(uid),
                'user_name': get_username_with_title(uid),
                'topic_num': topic_num,
                'vote_num': vote_num,
                'is_following': current_social_info.is_following_user(uid=uid) if current_user else False,
                'user_id': uid,
                'membership_level': get_membership_level_by_user_id(uid),
                'user_type': get_user_type_id(uid),
                'user_level': get_user_level(u),
            })
    except User.DoesNotExist:
        return gen(CODES.USER_NOT_FOUND)
    return {'following': result}


@bind_context("api/user/fans")
@list_interface(offset_name='start_num', limit_name='count', element_model=User)
def user_fans_list(ctx, user_id, count=10, start_num=0):
    """
    粉丝列表
    :param user_id:
    :param count:
    :param start_num:
    :return:
    """
    result = []

    if int(user_id) == 22 or start_num >= 1500:
        return {'fans': result}

    try:
        user = User.objects.get(pk=user_id)
        social_info = SocialInfo(uid=user.id)

        current_user = get_user_from_context(ctx)
        if current_user:
            current_social_info = SocialInfo(uid=current_user.id)

        uids = social_info.fans(start_num, count)
        for uid in uids:
            uid = int(uid)
            try:
                u = User.objects.get(pk=uid)
            except User.DoesNotExist:
                continue

            topic_num = int(u.person.topic_count)
            vote_num = int(u.person.vote_count)
            result.append({
                'portrait': get_portrait_by_user_id(uid),
                'user_name': get_username_with_title(uid),
                'topic_num': topic_num,
                'vote_num': vote_num,
                'is_following': current_social_info.is_following_user(uid=uid) if current_user else False,
                'user_id': uid,
                'membership_level': get_membership_level_by_user_id(uid),
                'user_type': get_user_type_id(uid),
                'user_level': get_user_level(u),
            })
    except User.DoesNotExist:
        return gen(CODES.USER_NOT_FOUND)
    return {'fans': result}


@bind_context("api/user/get_new_follow_num", login_required=True)
def user_new_follow_num(ctx):
    num = 0
    try:
        user = get_user_from_context(ctx)
        social_info = SocialInfo(uid=user.id)
        num = social_info.get_new_follow_count()
    except Exception:
        logging_exception()
    return num


@bind_context("api/user/add_follow", login_required=True)
def user_add_follow(ctx, user_id):
    """
    用户添加关注
    :param user_id: 需要关注的用户ID
    :return:
    """
    try:
        follow_user = User.objects.get(id=user_id)
        user = get_user_from_context(ctx)
        social_info = SocialInfo(uid=user.id)
        social_info.follow(uid=follow_user.id)
        user_name = get_user_by_id(user.id).last_name

        # push to fans list
        pushurl = gm_protocol.get_fan_lst(f_type='fans', user_id=user_id)
        extra = {
            'type': PUSH_INFO_TYPE.GM_PROTOCOL,
            'pushUrl': pushurl,
        }
        alert = u'{user_name}关注了你，你在站内贡献得到了更多人喜欢~'.format(user_name=user_name)
        # labels = {'event_type': 'push', 'event': 'got_new_fans'}
        allocate_push_task_users(user_id=user_id, extra=extra, alert=alert, push_type=AUTOMATED_PUSH.USER_IS_CONCERNED)

        event_data = send_event_task(user_id=user.id, event_type=EventType.FOLLOW_MULTI_TIMES, related_item=user_id)
        growth_value, point_value = event_data['growth_value'], event_data['point_value']
        submit_count = event_data['submit_count']
        return {'growth_value': growth_value, 'point_value': point_value, 'submit_count': submit_count}
    except User.DoesNotExist:
        logging_exception()
        return gen(CODES.USER_NOT_FOUND)
    except Exception:
        logging_exception()


@bind_context("api/user/clear_follow_num", login_required=True)
def user_clear_follow_num(ctx):
    try:
        user = get_user_from_context(ctx)
        social_info = SocialInfo(uid=user.id)
        social_info.clear_follow_num()
    except:
        logging_exception()


@bind_context("api/user/del_follow", login_required=True)
def user_del_follow(ctx, user_id):
    """
    用户取消关注
    :param user_id: 需要取消关注的用户ID
    :return:
    """
    try:
        follow_user = User.objects.get(id=user_id)
        user = get_user_from_context(ctx)
        social_info = SocialInfo(uid=user.id)
        social_info.unfollow(uid=follow_user.id)
    except User.DoesNotExist:
        logging_exception()
        return gen(CODES.USER_NOT_FOUND)
    except Exception:
        logging_exception()


@bind_context('api/user/game_login')
def api_user_game_login(ctx, phone):
    try:
        user = User.objects.get(username=phone)
    except User.DoesNotExist:
        user = User.objects.create(username=phone)
        ctx.logger.app(**user_register_log_data(user_id=user.id))
        user.last_name = phone[:2] + '*' * 7 + phone[9:]
        user.save()

        user_change_trigger(user.id)

        # 用户注册加美分
        point.add(user_id=user.id, reason=POINTS_TYPE.REGISTER)

        # 发送所长欢迎私信
        try:
            send_notification(
                uid=user.id, title=u'所长欢迎你', content=settings.WELCOME_CONTENT,
                url='gengmei://message_chat?user_key=22_{}'.format(user.id)
            )
            get_user_extra_by_user_id(user_id=user.id, phone=phone)
        except:
            logging_exception()

    login_response = {
        "uid": user.id,
        "error": 0,
    }
    return login_response


@bind_context('api/user/update_contact_phone', login_required=True)
def update_contact_phone(ctx, phone, code, new_version=False):
    # 从新版本开始用户联系方式的手机号不再校验
    # 因此如果发了验证码就校验,不发就不校验
    phone = format_phone(phone)
    if new_version is False:
        if not (phone and code):
            return gen(CODES.INVALID_CODE)

        code_valid = u"%04d" % calc_verification_code(phone) == code
        if not code_valid:
            return gen(CODES.INVALID_CODE)

    user = get_user_from_context(ctx)
    userextra = get_user_extra_by_user_id(user_id=user.id, phone=phone)
    if not userextra.phone:
        point.add(user_id=user.id, reason=POINTS_TYPE.REGISTER)

    userextra.phone = phone
    userextra.phone_last_modified = datetime.datetime.now()
    userextra.save()
    user_change_trigger(user_id=user.id)

    return True


@bind_context('api/user/config', login_required=True)
def user_config(ctx):
    user = get_user_from_context(ctx)
    userextra = get_user_extra_by_user_id(user_id=user.id)

    result = {
        'show_recommend_follow_tags': userextra.show_recommend_follow_tags,
    }

    # once call successfully, update field value as False.
    userextra.show_recommend_follow_tags = False
    userextra.save()

    user_change_trigger(user_id=user.id)

    return result


@bind_context('api/user/daren', login_required=False)
def get_daren_list(ctx, start_num=0, count=10):
    filters = {'membership_level': MEMBERSHIP_LEVEL.STAR}
    users = filter_user(offset=start_num, size=count, sort_type=USER_ORDER_TYPE.LAST_LOGIN, filters=filters)

    user_ids = users['user_ids']
    personinfos = Person.objects.filter(user_id__in=user_ids)

    user = get_user_from_context(ctx)
    if user:
        social = SocialInfo(uid=user.id)
        is_following = True
    else:
        is_following = False

    person = {}
    for personinfo in personinfos:
        data = personinfo.data()
        social_info = SocialInfo(uid=data['uid'])
        person[data['uid']] = {
            'user_id': data['uid'],
            'username': data['nickname'],
            'portrait': data['portrait'],
            'fans_count': social_info.fans_count if social_info.fans_count else 0,
            'topic_count': personinfo.topic_count if personinfo.topic_count else 0,
            'vote_count': personinfo.vote_count if personinfo.vote_count else 0,
            'is_following': is_following if not is_following else social.is_following_user(data['uid']),
            'membership_level': MEMBERSHIP_LEVEL.STAR,
        }

    result = []
    for user_id in user_ids:
        result.append(person[user_id])

    return result


@bind_context('api/user/get_user_by_referral_code')
def get_user_by_referral_code(ctx, referral_code):
    if not referral_code:
        return {}

    ue = {}

    try:
        user_extra = UserExtra.objects.get(referral_code=referral_code)

    except UserExtra.DoesNotExist:
        # http://sentry.gengmei.cc/sentry/prod-gaia/issues/253/
        return ue

    ue = {
        'user_id': user_extra.user.id,
        'referral_code': user_extra.referral_code,
    }
    return ue


@bind_context('api/user/get_user_name')
def get_target_user_name_by_uid(ctx, target_id):
    name = get_user_by_id(target_id).last_name
    target_doctor = get_doctor_by_user_id(target_id)
    if target_doctor and target_doctor.doctor_type == DOCTOR_TYPE.OFFICER:
        namelist = re.split(r'\s+', name)
        if len(namelist) >= 2 and namelist[-1] == '医生':
            namelist.pop()
            name = ''.join(namelist)
    result = {'name': name}
    return result


@bind('api/user/get_user_by_id')
@cache_page(5 * 60)
def get_user_streamline_info(target_id):
    """获取精简的用户信息， 不要在这个接口增加其他字段， 这个接口就是为了减少不必要的更多数据而创建的"""
    user = get_user_by_id(target_id)
    result = {
        'id': user.id,
        'portrait': filter_user_nick_name(user) or u'',
        'nickname': get_full_path(user.userextra.portrait) or get_full_path('img%2Fuser_portrait.png')
    }
    return result


@bind_context('api/user/get_user_by_doctor_id')
def get_user_id_by_doctor_id(ctx, target_id):
    user = get_user_by_doctor_id(target_id)
    if user is None:
        return gen(CODES.DOCTOR_NOT_FOUND)
    result = {'id': user.id}
    return result


@bind_context('api/user/extra', login_required=True)
def get_user_extra(ctx):
    user = get_user_from_context(ctx)
    user_id = user.id
    userextra_model = get_user_extra_by_user_id(user_id)
    userextra_data = userextra_model.to_dict()
    return userextra_data


@bind('api/user/get_user_extra_by_user_id')
def _get_user_extra_by_user_id(user_id):
    ue = get_user_extra_by_user_id(user_id)
    if not ue:
        return gen(CODES.USER_NOT_FOUND)

    userextra_data = ue.to_dict()
    return userextra_data


@bind_context('api/user/cityinfo')
def get_user_city_info(ctx, user_id=None, city_tag_required=False):
    if user_id is None:
        user = get_user_from_context(ctx)
        user_id = user.id

    ue = get_user_extra_by_user_id(user_id)
    if not ue:
        return gen(CODES.USER_NOT_FOUND)

    if ue.city is None:
        return gen(CODES.PROVINCE_DOES_NOT_EXIST)

    result = {
        'city': {
            'id': ue.city.id,
            'name': ue.city.name,
        }
    }

    if city_tag_required:
        result['city']['tag'] = ue.city.tag_id
        result['city']['tag'] = {
            'id': ue.city.tag_id,
        }

    return result


@bind_context('api/user/been_banned')
def check_user_in_black(ctx, device_id, idfv=None):
    # idfv 默认值为None而不为''的目的是, 由于device_id不唯一, 防止将未被拉黑的设备误判为拉黑设备
    user = get_user_from_context(ctx)
    if user:
        username = user._get_full_field_content_('username')
        if ((user and UserBlackList.objects.filter(bind_user=user).exists()) or (
                username and username in UserBlackList.objects.values_list('user', flat=True))):
            return gen(CODES.USER_IN_BLACKLIST)

    is_black_user = UserBlackList.objects.filter(device__device_id=device_id)
    if device_id and is_black_user and device_id not in settings.EXCEPT_BLACK_LIST:
        return gen(CODES.LOGIN_FAIL)


@bind_context('api/user/get_user_by_unionid')
def get_user_id_by_unionid(ctx, union_id):
    '''通过微信union_id 获取user_id'''

    user_ids = list(User.objects.filter(username=union_id).values_list('id', flat=True))
    if len(user_ids) != 1:
        raise gen(CODES.PERSON_NOT_FOUND)

    user_id = user_ids[0]

    result = {'id': user_id}
    return result


@bind_context('api/user/is_new_user')
def is_new_user(ctx, platform, device_id):
    user = get_user_from_context(ctx)
    return is_new_user_local(user=user, platform=platform, device_id=device_id)


@bind('api/user/by_doctor_id')
def user_by_doctor_id(id):
    model = Doctor
    try:
        d = model.objects.get(id=id)
        m = d.user
        if m is None:
            return gen(CODES.USER_NOT_FOUND)

        if hasattr(m, 'to_dict'):
            return m.to_dict()
        else:
            return to_dict(m)
    except model.DoesNotExist:
        return gen(CODES.DOCTOR_NOT_FOUND)


@bind_context('api/user/get_fundamental_info_by_user_ids')
@bind_context('api/user/get_fundamental_info_by_person_ids')
def get_user_fundamental_info(ctx, user_ids=None, person_ids=None):
    """get user info with limited extra infos.

    :param:
        user_ids: none or list of id

    :return: list of user info
        user info as {
            id, person_id, nickname, portrait,
            topic_count, vote_count
            membership_level, city_id, city_name, city_tag_id
        }
    """
    if not user_ids and not person_ids:
        user = get_user_from_context(ctx)
        if not user:
            return []

        user_ids = [user.id, ]

    if person_ids and not user_ids:
        persons = Person.objects.filter(id__in=person_ids)
        user_ids = list(persons.values_list('user_id', flat=True))

    if user_ids:
        users = User.objects.select_related('userextra', 'person', 'userextra__city').filter(id__in=user_ids)
    else:
        users = None

    if not users:
        return []

    user_extra_dict = {u.id: u.userextra for u in users}

    result = []
    for user in users:
        social = SocialInfo(uid=user.id)
        user_info = {
            'id': user.id,
            'person_id': user.person.id.hex,
            'nickname': filter_user_nick_name(user),
            'portrait': user.userextra.get_portrait(),
            'is_puppet': user.person.is_puppet,  # v 7.6.40 新加 判断当前用户是否是马甲用户

            'topic_count': user.person.topic_count,
            'vote_count': user.person.vote_count,
            'fans_count': social.fans_count,

            'membership_level': MEMBERSHIP_LEVEL.NORMAL,
            'current_city_id': None,
            'city_id': None,
            'city_name': None,
            'city_tag_id': None,
            'province_tag_id': None,
            'country_tag_id': None,
            'constellation': None,
            'user_rights_level': USER_RIGHTS_LEVEL.V1,
            'membership_icon': settings.MEMBERSHIP_IMG[MEMBERSHIP_LEVEL.NORMAL],
            'level_icon': settings.LEVEL_IMG[USER_RIGHTS_LEVEL.V1],
            'constellation_icon': '',
            'college_id': 0,  # 大学id
            'college_name': ""  # 大学名字
        }

        user_extra = user_extra_dict.get(user.id)
        if not user_extra:
            continue

        else:
            user_info.update({
                'membership_level': user_extra.membership_level,
                'current_city_id': user_extra.current_city_id or None,
                'city_id': user_extra.city and user_extra.city.id or None,
                'city_name': user_extra.city and user_extra.city.name or None,
                'city_tag_id': user_extra.city and user_extra.city.tag_id or None,
                'province_tag_id': user_extra.city and user_extra.city.province.tag_id or None,
                'country_tag_id': user_extra.city and user_extra.city.province.country.tag_id or None,
                'constellation': user_extra.constellation,
                'user_rights_level': user_extra.user_rights_level,
                'membership_icon': user_extra.membership_icon,
                'level_icon': user_extra.level_icon,
                'constellation_icon': user_extra.constellation_icon,
            })
            college = UserExtraToCollege.objects.filter(userextra=user_extra).values("college__id",
                                                                                     "college__name").last()  # 大学
            if college:
                user_info.update({
                    "college_id": college["college__id"],
                    "college_name": college["college__name"],
                })
        classify = ClassifyUser.objects.filter(user_id=user.id).values_list('classify', flat=True)
        user_info.update({
            'classify': list(classify)
        })
        result.append(user_info)

    return result


@bind('api/user/get_all_fans')
def get_all_fans(user_id):
    social = SocialInfo(uid=user_id)
    return social.fans(0, social.fans_count)


@bind_context("api/user/is_new_service_user")
def check_is_new_service_user(ctx):
    user = get_user_from_context(ctx)
    if in_new_user_whitelist(user):
        return True

    is_new_service_user = not user_have_payment(user)

    return is_new_service_user


@bind_context('api/user/following_tags')
def user_following_tags(ctx, user_id):
    """
    获取用户关注的tag
    如果当前用户已登录，判断当前用户是否关注了该tag
    未登录下默认未关注
    """
    current_user = get_user_from_context(ctx)
    current_user_tag_dict = {}
    if current_user:
        current_user_follow_tags = UserTagRelation.followed_tags_obj(current_user.id)
        current_user_tag_dict = {t.id: True for t in current_user_follow_tags}

    result = []
    tags = UserTagRelation.followed_tags_obj(user_id)
    for tag in tags:
        is_following = False
        if current_user_tag_dict.get(tag.id):
            is_following = True

        result.append({
            'tag_id': tag.id,
            'tag_name': tag.name,
            'icon': tag.icon_url,
            'desc': tag.description,
            'is_following': is_following,
        })

    return result


@bind('api/user/update_level_list')
def update_user_level(users_list):
    #  批量修改用户权益等级
    # 当前为mimas中的回调对应服务
    for user in users_list:
        UserExtra.objects.filter(user_id=user['user_id']).update(user_rights_level=user['level'])
        user_change_trigger(user['user_id'])
    return


@bind('api/user/update_current_city_id')
def update_user_current_city_id(user_id, city_id, device_id=""):
    result = {'updated': False}

    try:
        ue = UserExtra.objects.get(user_id=user_id)
    except UserExtra.DoesNotExist:
        return result

    try:
        # check city id is valid
        city = City.objects.get(id=city_id)
    except City.DoesNotExist:
        return result

    if device_id:
        DeviceBinding.objects.update_or_create(user=ue.user, defaults={
            'device': device_id, 'city': city, 'province': city.province
        })

        # if city id doesnt change, return
    if ue.current_city_id == city_id:
        return result

    ue.current_city_id = city_id
    ue.save()
    user_change_trigger(user_id)

    result['updated'] = True
    return result


@bind_context('api/user/checkin')
def usr_checkin_data(ctx):
    current_user = get_user_from_context(ctx)
    data = UserCheckinLog.get_checkin_result(current_user)
    return data


@bind_context('api/user/if_checkin')
def if_usr_checkin(ctx):
    current_user = get_user_from_context(ctx)
    data = {}
    data['is_checked_today'] = UserCheckinLog.is_checked_today(current_user)
    return data


@bind('api/user/is_user_in_blacklist')
def if_user_in_blacklist(user_id):
    if ShortvideoBlackList.objects.filter(user_id=user_id, is_online=True).exists():
        return True

    return False


@bind_context('api/user/authorize_check', login_required=True)
def usr_check_authorize(ctx):
    current_user = get_user_from_context(ctx)
    if not UserAuthentication.objects.filter(user_id=current_user.id).exists():
        return gen(CODES.USER_NEED_AUTHORIZE)
    return gen(CODES.SUCCESS)


@bind_context('api/user/authorize_add')
def usr_authorize_add(ctx, phone, user_id, op_type, vcode=None):
    if op_type == 'insert':
        phone_f = format_phone(phone)
        verify = VerifiedCode.verify(vcode, phone=phone_f, code_type=VERIFY_CODE_TYPE.USER_AUTHENTICATION)
        if not verify:
            return gen(CODES.INVALID_CODE)
        if phone_f and user_id:
            UserAuthentication.objects.get_or_create(phone=phone_f, user_id=user_id)
        else:
            return gen(CODES.PARAMS_INCOMPLETE)
    elif op_type == 'update':
        if phone:
            phone_f = format_phone(phone)
            current_user = get_user_from_context(ctx)
            try:
                user_auth = UserAuthentication.objects.filter(user_id=current_user.id)
                if user_auth:
                    user_auth.update(phone=phone_f)
                else:
                    UserAuthentication.objects.get_or_create(phone=phone_f, user_id=current_user.id)
            except UserAuthentication.DoesNotExist:
                return gen(CODES.UPDATE_FAIL)
        else:
            return gen(CODES.PARAMS_INCOMPLETE)
    elif op_type == 'insert_by_register':
        phone_f = format_phone(phone)
        if phone_f and user_id:
            UserAuthentication.objects.get_or_create(phone=phone_f, user_id=user_id)
        else:
            return gen(CODES.PARAMS_INCOMPLETE)
    return gen(CODES.SUCCESS)


@bind_context("api/user/update_college")
def update_user_college_info(ctx, college_id):
    """
    通过大学及用户信息，更新用户对应的大学信息
    :param ctx:
    :param college_id: 大学的id
    :return:
    """
    result = {
        "status": False  # 更新的状态
    }

    user = get_user_from_context(ctx)
    userextra = get_user_extra_by_user_id(user_id=user.id)

    if not userextra:
        return gen(CODES.USER_NOT_FOUND)

    try:
        college = College.objects.get(pk=college_id, is_online=True)
    except College.DoesNotExist:
        return result

    _, created = UserExtraToCollege.objects.get_or_create(userextra=userextra, college=college)
    result["status"] = created
    return result


@bind("api/user/info_for_trilateral")
@cache_page(24 * 60 * 60)
def get_user_info_for_trilateral(user_id):
    """
    用于通过 user_id 获取三方合作的用户基本信息
    :param user_id:
    :return:
    """
    result = {}
    if not user_id:
        return result
    try:
        user = User.objects.get(pk=user_id)
    except User.DoesNotExist:
        return result

    return {
        "user_id": user.id,
        "raw_auth_name": user.username,
    }


@bind('api/user/info_by_user_name')
def get_user_id_by_user_name(user_name):
    users = User.objects.using('slave').filter(last_name__contains=user_name)
    return {'user_ids': [item.id for item in users]}


@bind('api/weixin_user/get')
def get_weixin_info(user_id):
    if not user_id:
        return {}

    weixin_user = PlatformUser.objects.filter(user_id=int(user_id), platform_type__in=[LOGIN_AUTH_TYPE.Wechat]).first()

    if not weixin_user:
        return {}

    try:
        extra_info = json.loads(weixin_user.extra_info)
    except:
        extra_info = {}

    return {
        "user_id": user_id,
        "avatar": extra_info.get('avatarUrl'),
        "user_name": extra_info.get('nickName'),
    }


@bind('api/user/cpc_user_info')
def get_cpc_user_info(user_id):
    user = User.objects.get(id=user_id)
    info = {
        "nick_name": filter_user_nick_name(user) or u'',
    }
    age = None
    try:
        user_extra = UserExtra.objects.get(user=user)
        age = user_extra.age
    except:
        pass
    info['age'] = age
    return info


@bind('api/user_classify/ids')
def get_user_ids_by_classifyuser():
    user_ids = list(ClassifyUser.objects.using(settings.SLAVE_DB_NAME).filter(
        classify=USER_CLASSIFY.MODEL).values_list('user_id', flat=True))

    return {
        'user_ids': user_ids
    }


@bind("api/user/phone/ids")
def get_user_phone_by_ids(user_ids, need_phone=True):
    """保护私密信息"""
    user_phone_data = get_phone_by_user_ids(user_ids=user_ids)
    if not need_phone:
        bind_phone_data = {key: True if value else False for key, value in user_phone_data.items()}
        return bind_phone_data

    return user_phone_data


@bind('api/userlist/query')
def get_userlist(doctor_user_id, nickname, user_id, list_type='0',  page='1',size=10):
    order_index={'0':'browse_time','1':'order_validated_time','2':'order_pay_time','3':'conversation_created_time'}
    exclude_condition={'1':'order_validated_id','2':'order_pay_id','3':'conversation_id'}
    doctor_id = Doctor.objects.get(user_id=doctor_user_id).id
    merchant_id = MerchantRelevance.objects.get(doctor_id=doctor_id).merchant_id
    list_type = str(list_type)
    filter_fields={'merchant_id':merchant_id}
    if list_type !='0':
        filter_fields[exclude_condition[list_type]+'__isnull']=False
    userlist=[]
    if user_id:
        filter_fields['user_id']=user_id
    if nickname:
        users = User.objects.filter(last_name__contains=nickname)
        user_ids=[]
        for user in users:
            user_ids.append(user.id)
        filter_fields['user_id__in']=user_ids
    user_objs = UserList.objects.filter(**filter_fields).order_by('-%s'%order_index.get(list_type))

    for user_obj in user_objs[size*(page-1):size*page]:
        pay_order = Order.objects.filter(id=user_obj.order_pay_id).first()
        validate_order = Order.objects.filter(id=user_obj.order_validated_id).first()
        browse = MerchantPvStat.objects.filter(id=user_obj.browse_id).first()
        conversationstatueuser = ConversationUserStatus.objects.filter(conversation_id=user_obj.conversation_id, user_id=user_obj.doctor_user_id).first()

        pay_order_info=validate_order_info=consume_info=conversation_info=consume_info=browse_info={}
        if user_obj.amount:
            consume_info = {'amount': user_obj.amount}
        if browse:
            browse_info = {'browse_time':datetime.datetime.strftime(browse.browsing_time,"%Y-%m-%d %H:%M:%S") if browse.browsing_time else '','browse_page':browse.page_name,'page_type':USER_BROWSING_TYPE.getDesc(browse.page_type)}
        if pay_order:
            try:
                sku_name = "".join(json.loads(pay_order.servicesnapshot.items)) if pay_order.servicesnapshot.items else ''
            except:
                sku_name = ""
            pay_order_info = {'amount':pay_order.real_payment,'service_name':pay_order.service.name,'sku_name':sku_name, 'pay_time':datetime.datetime.strftime(pay_order.pay_time,"%Y-%m-%d %H:%M:%S") if pay_order.pay_time else ''}
        if validate_order:
            try:
                sku_name = "".join(json.loads(validate_order.servicesnapshot.items)) if validate_order.servicesnapshot.items else ''
            except:
                sku_name = ""
            validate_order_info = {'amount': validate_order.real_payment, 'service_name': validate_order.service.name,'sku_name':sku_name,'validate_time': datetime.datetime.strftime(validate_order.validate_time, "%Y-%m-%d %H:%M:%S") if validate_order.validate_time else ''}
        if conversationstatueuser:
            conversation_info = {'conversation_name':Doctor.objects.get(user_id=user_obj.doctor_user_id).name,'remark':conversationstatueuser.comment}
            conversation_info['conversation_time'] = datetime.datetime.strftime(conversationstatueuser.conversation.created_time,"%Y-%m-%d %H:%M:%S")
        """
        用户信息
        """
        user_extra = UserExtra.objects.get(user_id=user_obj.user_id)
        user=User.objects.get(id=user_obj.user_id)
        user_info = {'user_id':user.id,'location':user_extra.city.name if user_extra.city else '','nickname':user.last_name if user.last_name else ''}
        userlist.append({'user_info':user_info,'pay_order_info':pay_order_info,'validate_order_info':validate_order_info,'browse_info':browse_info,'consume_info':consume_info,'conversation_info':conversation_info})
    data={'userlist':userlist,'total':user_objs.count(), 'size':size, 'page':int(page)}
    return {'total':user_objs.count(), 'size':size, 'page':int(page), 'userlist':userlist}


@bind('api/userlist/detail')
def get_user_detail(doctor_user_id, user_id, detail_type='0',  page=1,size=10):
    doctor_id = Doctor.objects.get(user_id=doctor_user_id).id
    merchant_id = MerchantRelevance.objects.get(doctor_id=doctor_id).merchant_id
    doctor_list = MerchantRelevance.objects.filter(merchant_id=merchant_id).values_list('doctor_id')
    doctor_ids = []
    result=[]
    total = 0
    detail_type = str(detail_type)
    for item in doctor_list:
        doctor_ids.append(item[0])

    # 验证订单信息
    if detail_type =='0':
        orders=Order.objects.filter(user_id=user_id,service__doctor_id__in=doctor_ids,validated=True).order_by('-validate_time')
        total = orders.count()
        for order in orders[size*(page-1):size*page]:
            sku_name = "".join(json.loads(order.servicesnapshot.items)) if order.servicesnapshot.items else ''
            validate_time = datetime.datetime.strftime(order.validate_time,"%Y-%m-%d %H:%M:%S") if order.validate_time else ''
            result.append({'validate_num':order.id, 'service_name':order.service.name,'sku_name':sku_name, 'validate_amount':order.real_payment, 'validate_time':validate_time,'validate_status':order.validated})

    # 订单支付记录信息
    elif detail_type =='1':
        orders = Order.objects.filter(user_id=user_id, service__doctor_id__in=doctor_ids, status=ORDER_STATUS.PAID).order_by('-pay_time')
        total = orders.count()
        for order in orders[size*(page-1):size*page]:
            try:
                sku_name = "".join(json.loads(order.servicesnapshot.items)) if order.servicesnapshot.items else ''
            except:
                sku_name = ""
            pay_time = datetime.datetime.strftime(order.pay_time,"%Y-%m-%d %H:%M:%S") if order.pay_time else ''
            result.append({'pay_num':order.id, 'service_name':order.service.name,'sku_name':sku_name, 'pay_amount':order.real_payment, 'pay_time':pay_time,'pay_status':ORDER_STATUS.getDesc(order.status)})

    # 下单记录信息
    elif detail_type == '2':
        orders = Order.objects.filter(user_id=user_id, service__doctor_id__in=doctor_ids, status=ORDER_STATUS.NOT_PAID).order_by('-created_time')
        total = orders.count()
        for order in orders[size*(page-1):size*page]:
            try:
                sku_name = "".join(json.loads(order.servicesnapshot.items)) if order.servicesnapshot.items else ''
            except:
                sku_name = ""
            created_time = datetime.datetime.strftime(order.created_time,"%Y-%m-%d %H:%M:%S") if order.created_time else ''
            result.append({'order_num':order.id, 'service_name':order.service.name,'sku_name':sku_name, 'order_amount':order.service_price, 'order_created_time':created_time,'order_status':ORDER_STATUS.getDesc(order.status)})

    #咨询记录信息api_conversation_user
    elif detail_type == '3':
        doctor_user_ids = []
        hash_user_ids = []
        doctors = Doctor.objects.filter(id__in=doctor_ids)
        for doctor in doctors:
            if doctor.user_id:
                doctor_user_ids.append(doctor.user_id)
                hash_user_id = Conversation.gen_uid_hash([int(user_id),int(doctor.user_id)])
                hash_user_ids.append(hash_user_id)
        conversations = Conversation.objects.filter(uid_hash__in=hash_user_ids)
        conversation_ids = []
        total = conversations.count()
        for conversation in conversations:
            conversation_ids.append(conversation.id)
        conversationstatueusers = ConversationUserStatus.objects.filter(conversation_id__in=conversation_ids,user_id__in=doctor_user_ids).order_by('-conversation__created_time')
        for conversationstatueuser in conversationstatueusers[size*(page-1):size*page]:
            conversation_info = {'conversation_name': Doctor.objects.get(user_id=conversationstatueuser.user_id).name,'conversation_remarks': conversationstatueuser.comment}
            conversation_info['conversation_start'] = datetime.datetime.strftime(conversationstatueuser.conversation.created_time,"%Y-%m-%d %H:%M:%S")
            conversation_info['conversation_end'] = datetime.datetime.strftime(conversationstatueuser.conversation.last_reply_time,"%Y-%m-%d %H:%M:%S")
            result.append(conversation_info)

    #浏览记录信息
    elif detail_type == '4':
        browses = MerchantPvStat.objects.filter(user_id=user_id,merchant_id=merchant_id).order_by('-browsing_time')
        total = browses.count()
        for browse in browses[size*(page-1):size*page]:
            result.append({'browse_time':datetime.datetime.strftime(browse.browsing_time,"%Y-%m-%d %H:%M:%S") if browse.browsing_time else '','page_name':browse.page_name,'page_type':USER_BROWSING_TYPE.getDesc(browse.page_type)})

    """
    用户信息
    """
    user_extra = UserExtra.objects.get(user_id=user_id)
    user = User.objects.get(id=user_id)
    nickname = user.last_name if user.last_name else ''
    birth = datetime.datetime.strftime(user_extra.birthday,"%Y-%m-%d %H:%M:%S") if user_extra.birthday else ''
    city = user_extra.city.name if user_extra.city else ''
    register_time = datetime.datetime.strftime(user_extra.create_time,"%Y-%m-%d %H:%M:%S") if user_extra.create_time else ''
    last_login = datetime.datetime.strftime(user_extra.last_login,"%Y-%m-%d %H:%M:%S") if user_extra.last_login else ''
    image = user_extra.portrait if user_extra.portrait else ''
    if image:
        image = get_full_path(image, ('-w', '-thumb'))
    user_info = {'nickname':nickname, 'user_id':user_id, 'birth':birth, 'city':city, 'register_time':register_time, 'last_login':last_login, 'image':image}

    return {'size':size, 'page':int(page), 'total':total, 'detail_type':detail_type, 'user_info':user_info, 'result':result}


@bind('api/user/protect_phone')
def user_protect_phone(operator, event_type=None):
    if event_type:
        objs = ProtectPhone.objects.filter(operator=operator, event_type=event_type, is_online=True)
    else:
        objs = ProtectPhone.objects.filter(operator=operator, is_online=True)
    ret = [{
        'id': obj.id,
        'event_id': obj.event_id,
        'event_type': obj.event_type
    } for obj in objs] if objs else []
    return ret
