# coding:utf8

import re
import uuid

from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.db import transaction
from django.contrib.auth.models import User
from gm_types.doctor import DOCTOR_REGISTER_STATUS

from gm_types.gaia import (
    LOGIN_KIND,
    POINTS_TYPE,
)

import point

from api.tool.person import create_user_by_email, create_person_by_email
from api.tool.person import create_user_by_phone, create_person_by_phone
from api.tool.person import update_password
from hippo.models import DoctorRegister
from rpc.decorators import bind_context
from rpc.decorators import bind
from rpc.cache import code_cache
from rpc.tool.error_code import CODES, gen

from api.tool.verification_code import anti_brute, VerifiedCode
from api.tool.user_tool import get_user_from_context
from api.tool.common_tool import check_platform
from api.models import Person, Area, validate_password
from api.models import FenxiaoPersonWanghong
from api.models import VERIFY_CODE_TYPE, ANTI_BRUTE_TYPE, PASSWORD_CHANGE_TYPE
from api.trigger.user_trigger import user_change_trigger
from api.tasks.user_related_tasks import (
    update_device_pushinfo,
    record_device_info,
    set_city_by_lat_lng_or_cityname,
)

from api.models import PLATFORM_CHANNEL
from api.models.doctor import Doctor
from utils.phone import format_phone


@transaction.atomic
@bind_context("api/person/create/with/phone")
def create_person_quietly_with_phone(ctx, phone):
    """ 线下分期静默创建用户
    """
    phone = format_phone(phone)
    user = create_user_by_phone(ctx, phone)
    if not user:
        gen(CODES.PERSON_EXIST)

    user_change_trigger(user.id)

    person = create_person_by_phone(phone, user, None, None, None)
    point.add(user_id=user.id, reason=POINTS_TYPE.REGISTER)
    return _login(ctx, person)


@transaction.atomic
@bind_context("api/person/create")
def create_person(ctx, code, phone="", account="", password=None,
                  platform=PLATFORM_CHANNEL.UNKNOWN,
                  device_info={}, wanghong_id=None,
                  lng=None, lat=None, current_city_id=None, login_type=LOGIN_KIND.PHONE, area_id=None):
    """
    area_id 默认为中国大陆
    """
    if area_id is None:
        area_id = Area.objects.get(phone_prefix="+86").id

    account = format_phone(phone or account)

    if login_type not in [LOGIN_KIND.PHONE, LOGIN_KIND.EMAIL]:
        return gen(CODES.REGISTER_TYPE_ERROR)

    if anti_brute(account):
        return gen(CODES.USER_MAX_RETIES)

    account = account.strip()
    key = "phone:%d:%s" % (VERIFY_CODE_TYPE.CREATE, account)
    verified_code = code_cache.get(key)

    if verified_code is None or verified_code != code:
        return gen(CODES.INVALID_CODE)

    if login_type == LOGIN_KIND.PHONE:
        user = create_user_by_phone(ctx, account)
    elif login_type == LOGIN_KIND.EMAIL:
        user = create_user_by_email(ctx, account)

    if not user:
        gen(CODES.PERSON_EXIST)

    user_change_trigger(user.id)

    if login_type == LOGIN_KIND.PHONE:
        person = create_person_by_phone(account, user, platform, password, area_id)
    elif login_type == LOGIN_KIND.EMAIL:
        person = create_person_by_email(account, user, platform, password, area_id)

    point.add(user_id=user.id, reason=POINTS_TYPE.REGISTER)

    if device_info:
        record_device_info(user_id=user.id, **device_info)
        update_device_pushinfo.delay(user_id=user.id, **device_info)

    set_city_by_lat_lng_or_cityname.delay(
        user.id, lat, lng, None, current_city_id
    )

    # TODO: pengfei.x, remove fengxiaowanghong logic, f
    if wanghong_id is not None and login_type == LOGIN_KIND.PHONE:
        FenxiaoPersonWanghong.create(person.id, wanghong_id)
    return _login(ctx, person)


@bind_context("api/person/login")
def login_person(ctx, code, phone="", account="", platform=PLATFORM_CHANNEL.UNKNOWN, device_info={},
                 login_type=LOGIN_KIND.PHONE):
    account = format_phone(phone or account)

    if account in ('18810225027', '12345678912', '12345678888') and code == "1234":
        person = Person.objects.get(phone=account)
        return _login(ctx, person)

    if anti_brute(account + ANTI_BRUTE_TYPE.LOGIN_PHONE):
        return gen(CODES.USER_MAX_RETIES)

    verify = VerifiedCode.verify(code, phone=account, code_type=VERIFY_CODE_TYPE.LOGIN)
    if not verify:
        return gen(CODES.INVALID_CODE)

    try:
        if login_type == LOGIN_KIND.PHONE:
            person = Person.objects.get(phone=account)
        elif login_type == LOGIN_KIND.EMAIL:
            # 邮箱登录在PC端入口已不再支持，可废弃此登录方式 at 2017-02-15
            person = Person.objects.get(email=account)

        platform = check_platform(platform)
        person.platform = platform
        person.save()
        user_change_trigger(person.user_id)

        if device_info:
            user = person.user
            record_device_info(user_id=user.id, **device_info)
            update_device_pushinfo.delay(user_id=user.id, **device_info)

        return _login(ctx, person)

    except Person.DoesNotExist:
        return gen(CODES.PERSON_NOT_FOUND)


@bind_context("api/person/reset_password", login_required=True)
def reset_password(ctx):
    user = get_user_from_context(ctx)
    try:
        person = user.person
        person.password = u''
        person.save()
        user_change_trigger(user.id)
        return
    except Person.DoesNotExist:
        return gen(CODES.PERSON_NOT_FOUND)


@bind_context('api/person/login_password')
def login_password(ctx, password, phone="", account="", platform=PLATFORM_CHANNEL.UNKNOWN, device_info={},
                   login_type=LOGIN_KIND.PHONE):
    if login_type not in [LOGIN_KIND.PHONE, LOGIN_KIND.EMAIL]:
        gen(CODES.LOGIN_FAIL)

    account = format_phone(phone or account)

    password = password.strip()
    if anti_brute(account + ANTI_BRUTE_TYPE.LOGIN_PASSWORD):
        return gen(CODES.USER_MAX_RETIES)

    try:
        if login_type == LOGIN_KIND.PHONE:
            person = Person.objects.get(phone=account)
        elif login_type == LOGIN_KIND.EMAIL:
            person = Person.objects.get(email=account)

        if not person.password:
            # 不要出现可能暴露用户数据的提示
            # return gen(CODES.PLEASE_RESET_PASSWORD)
            return gen(CODES.PHONE_PASSWORD_INVALID)

        # 由于医生端鉴权用的是user表,用户端鉴权用的person表,需要双写
        elif validate_password(password, person):
            platform = check_platform(platform)
            person.platform = platform
            person.save()

            user_change_trigger(person.user_id)

            if device_info:
                user = person.user
                record_device_info(user_id=user.id, **device_info)
                update_device_pushinfo.delay(user_id=user.id, **device_info)

            return _login(ctx, person)

        return gen(CODES.PHONE_PASSWORD_INVALID)

    except Person.DoesNotExist:
        return gen(CODES.PHONE_PASSWORD_INVALID)


def _login(ctx, person):
    person.user.backend = "django.contrib.auth.backends.ModelBackend"
    ctx.session.do_login(person.user)
    data = person.data(True)
    data["session_key"] = ctx.session.session_key
    data["login_user_id"] = person.user.id
    return data


@bind_context('api/person/info', login_required=True)
def get_person_info(ctx):
    user = get_user_from_context(ctx)
    return user.person.data()


@bind_context("api/person/change_password", login_required=True)
def change_password(ctx, password, new_password, repeat_password,
                    change_type=PASSWORD_CHANGE_TYPE.CHANGE_PASSWORD):
    """
    NOTE:
        Desc： 更改密码

    PARAMETERS:
        password: 老密码
        new_password: 新密码
        repeat_password: 重复输入新密码
        change_type: CHANGE_PASSWORD 修改密码
                     SET_PASSWORD 重置密码
    """
    user = get_user_from_context(ctx)
    person = user.person
    password = password.strip()
    new_password = new_password.strip()
    repeat_password = repeat_password.strip()
    account = str(person.email or person.phone or person.user_id)

    if anti_brute(account + ANTI_BRUTE_TYPE.FIND_PASSWORD):
        return gen(CODES.USER_MAX_RETIES_CHANGE)

    if not new_password or not repeat_password:
        return gen(CODES.PASSWORD_IS_EMPTY)

    elif new_password != repeat_password:
        return gen(CODES.PASSWORD_IS_DIFFERENT)

    elif not check_password_format(new_password):
        return gen(CODES.PASSWORD_FORMAL_ERROR)

    elif check_password(new_password, person.password):
        return gen(CODES.PASSWORD_IS_SAME)

    elif change_type == PASSWORD_CHANGE_TYPE.CHANGE_PASSWORD:
        if person.password and not check_password(password, person.password):
            return gen(CODES.PASSWORD_INVALID)

    update_password(person, new_password)
    user_change_trigger(user.id)
    return person.data()


def check_password_format(password):
    '''
        NOTE:判断密码格式是否正确
    '''
    regex = re.compile('^[\w]{6,16}$')
    return regex.match(password)


@bind_context('api/person/user_info')
def get_person_user_info(ctx, person_id):
    try:
        person = Person.objects.get(pk=person_id)
    except Person.DoesNotExist:
        return gen(CODES.PERSON_NOT_FOUND)

    return {
        'user_id': person.user_id,
    }


@bind('api/person/incr_vote')
def incr_vote_to_person(user_id=None, person_id=None):
    if user_id is not None:
        try:
            u = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        u.person.incr_vote_count()
    elif person_id is not None:
        try:
            p = Person.objects.get(id=person_id)
        except Person.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        p.incr_vote_count()


@bind('api/person/decr_vote')
def decr_vote_to_person(user_id=None, person_id=None):
    if user_id is not None:
        try:
            u = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        u.person.decr_vote_count()
    elif person_id is not None:
        try:
            p = Person.objects.get(id=person_id)
        except Person.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        p.decr_vote_count()


@bind('api/person/incr_topic_count')
def incr_topic_count_to_person(user_id=None, person_id=None):
    if user_id is not None:
        try:
            u = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        u.person.incr_topic_count()
    elif person_id is not None:
        try:
            p = Person.objects.get(id=person_id)
        except Person.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        p.incr_topic_count()


@bind('api/person/decr_topic_count')
def decr_topic_count_to_person(user_id=None, person_id=None):
    if user_id is not None:
        try:
            u = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        u.person.decr_topic_count()
    elif person_id is not None:
        try:
            p = Person.objects.get(id=person_id)
        except Person.DoesNotExist:
            return gen(CODES.USER_NOT_FOUND)
        p.decr_topic_count()


@bind('api/person/if_person_is_doctor')
def is_person_is_doctor(person_id):
    try:
        uuid.UUID(person_id)
    except ValueError:
        return False

    is_person_doctor = Doctor.is_person_doctor(person_id)
    return is_person_doctor


@bind('api/person/is_doctor_by_person_ids')
def is_doctor_by_person_ids(person_ids):
    """
    is doctor by person_ids

['{12345678-1234-5678-1234-567812345678}',
'12345678-1234-5678-1234-567812345678',
'12345678123456781234567812345678',
'urn:uuid:12345678-1234-5678-1234-567812345678',
'0446266eca4c11e5903d0242c0a82a2d','0446266e-ca4c-11e5-903d-0242c0a82a2d']

->

{'{12345678-1234-5678-1234-567812345678}': False,
 '12345678-1234-5678-1234-567812345678': False,
 '0446266e-ca4c-11e5-903d-0242c0a82a2d': True,
 'urn:uuid:12345678-1234-5678-1234-567812345678': False,
 '12345678123456781234567812345678': False,
 '0446266eca4c11e5903d0242c0a82a2d': True}


    :param person_ids:
    :return:
    """

    valid_person_ids = set()
    for person_id in person_ids:
        try:
            uuid.UUID(person_id)
            valid_person_ids.add(person_id)
        except ValueError:
            pass

    uuid_to_is_doctor = {}

    person_uuid_to_user_id = {uuid: uid for uuid, uid in Person.objects.filter(
        pk__in=valid_person_ids).values_list('id', 'user_id')}

    is_doctor_user_ids = set(Doctor.objects.filter(
        user_id__in=person_uuid_to_user_id.values()).values_list("user_id", flat=True))

    for person_uuid, uid in person_uuid_to_user_id.items():
        uuid_to_is_doctor[person_uuid] = True if uid in is_doctor_user_ids else False

    result = {}
    for person_id in person_ids:
        is_doctor = False
        if person_id in valid_person_ids:
            person_uuid = uuid.UUID(person_id)
            if person_uuid in uuid_to_is_doctor:
                is_doctor = uuid_to_is_doctor[person_uuid]
        result[person_id] = is_doctor

    return result


@bind('api/person/exists')
def is_person_exists(phone):
    phone = format_phone(phone)
    return Person.objects.filter(phone=phone).exists()


@bind('api/person/get_by_phone')
def get_person_by_phone(phone):
    persons = Person.objects.filter(phone=phone)
    if persons.exists():
        person_id = str(persons.first().id)
        doctorregisters = DoctorRegister.objects.filter(person_id=person_id)\
            .exclude(status=DOCTOR_REGISTER_STATUS.DATA_REJECT)
        if doctorregisters.exists():
            is_register = True
        else:
            is_register = False
        doctor = Doctor.objects.filter(user__person__id=person_id)
        if doctor.exists():
            is_doctor = True
        else:
            is_doctor=False
        return {
            "id": person_id,
            "is_register": is_register,
            "is_doctor": is_doctor,
            "user_id": persons.first().user_id
        }
    else:
        return None


@bind('api/person/user_id_2_phone')
def get_user_id_2_phone(user_id_list):
    user_id_2_phone = {}
    user_id_phone = Person.objects.filter(user_id__in=user_id_list).values_list('user_id','phone')
    for user_id, phone in user_id_phone:
        user_id_2_phone[user_id] = phone

    return user_id_2_phone


@bind('api/person/puppet_user')
def get_puppet_user_ids():
    """ hera 后台筛选马甲号 """
    puppet_ids = Person.objects.using(settings.HERA_READ_DB).filter(is_puppet=True).values_list('user_id', flat=True)
    return {'puppet_ids': list(puppet_ids)}
