# coding=utf8

from __future__ import unicode_literals, absolute_import, print_function

import datetime
import random
import time

from django.conf import settings
from django.db.models import Q
from django.db import transaction

from gm_types.gaia import (
    TAG_TYPE,
    POINTS_TYPE,
    DIARY_OPERATE,
    DIARY_OPERATION,
    TOPIC_TYPE,
)
from gm_upload.utils.image_utils import Picture

from talos.libs.diaryrank_utils import DiaryRankTool
from talos.logger import info_logger
from talos.manager.diary import (
    set_diary_cover,
    set_diary_postoperation_cover,
)
from talos.models.diary import (
    Diary,
    DiaryTag,
    DiaryTagV3,
    PreOperationImage,
    DiaryExtra,
)
from talos.models.topic import (
    Problem,
    TopicImage,
    ProblemTag,
)
from talos.services import (
    TagService,
    DoctorService,
    HospitalService,
    OrderService,
)
from talos.services.other import add_points
from talos.tasks.topic import async_diary_change_tags_to_topic
from utils.rpc import logging_exception

def get_diary_title(tags):

    tags.sort(key=lambda i: i.id)

    def get_type_tags(tags, tag_type):
        return list(filter(lambda i: i.tag_type == tag_type, tags))

    tag_list = get_type_tags(tags, TAG_TYPE.ITEM_WIKI)
    if not tag_list:
        tag_list = get_type_tags(tags, TAG_TYPE.BODY_PART_SUB_ITEM)
    if not tag_list:
        tag_list = get_type_tags(tags, TAG_TYPE.BODY_PART)

    if tag_list:
        return tag_list[0].name + '日记'

    return None


def update_diary_extra_info(diary_ids: list):
    """更新日记本点统计信息。"""

    if not diary_ids:
        return

    diaries = Diary.objects.filter(Q(extra_info__isnull=False) &
                                   Q(id__in=diary_ids))
    if not diaries:
        return

    for diary in diaries:
        diary.extra_info.vote_count = diary.vote_num
        diary.extra_info.topic_count = diary.topic_num
        diary.extra_info.total_pv = diary.view_num
        diary.extra_info.reply_count = diary.reply_num
        diary.extra_info.save()


TAG_CITY_TYPE_LIST = [TAG_TYPE.COUNTRY, TAG_TYPE.PROVINCE, TAG_TYPE.CITY]  # 城市类型标签
TAG_ITEM_TYPE_LIST = [TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI]  # 1、2、3级标签


def common_func_for_create_diary(
        user,
        title="",
        comment=[],
        order_id=None,
        tag_ids=[],
        operation_timestamp=None,
        doctor_id=None,
        doctor_name=None,
        hospital_id=None,
        hospital_name=None,
        service_id=None,
        price=0,
        rating=None,
        operation_effect_rating=0,
        doctor_attitude_rating=0,
        hospital_env_rating=0,
        pre_operation_images=[],
        cover=None,
        create_for_draft=False,
        new_version_create=False
):
    """
    创建日记本的公共方法
    :param user:
    :param title:日记本的标题,7.7.20日记本可编辑
    :param comment:
    :param order_id:
    :param tag_ids:
    :param operation_timestamp:
    :param doctor_id:
    :param doctor_name:
    :param hospital_id:
    :param hospital_name:
    :param service_id:
    :param price:
    :param rating:
    :param operation_effect_rating:
    :param doctor_attitude_rating:
    :param hospital_env_rating:
    :param pre_operation_images:
    :param cover:
    :param create_for_draft: 是否为新草稿创建的日记本
    :param new_version_create:  是否是新版创建
    :return:
    """
    diary = Diary()
    diary.user_id = user.id
    diary.price = price

    diary, tag_ids, update_order_info = _update_diary_operation_info(
        diary,
        tag_ids=tag_ids,
        order_id=order_id,
        doctor_id=doctor_id,
        doctor_name=doctor_name,
        hospital_id=hospital_id,
        hospital_name=hospital_name
    )

    if operation_timestamp:
        operation_time = datetime.date.fromtimestamp(
            float(operation_timestamp)
        )
        diary.operation_time = operation_time
        diary.is_fake_operation_time = False
    else:
        diary.operation_time = datetime.datetime.now()
        diary.is_fake_operation_time = True

    if comment:
        diary.comment = comment

    if comment or diary.rating:
        add_points(reason_type=POINTS_TYPE.REPLY_DIARY)

    diary.save()

    has_geo_tag = False
    tags = TagService.get_tags_by_tag_ids(tag_ids)
    # 新版创建日记本,最多绑定 10个 标签
    if new_version_create:
        exclude_city_tags = list(filter(
            lambda t: t.tag_type not in TAG_CITY_TYPE_LIST, tags))[:settings.DIARY_TAGS_MAX_COUNT]
        tags = exclude_city_tags + list(filter(lambda x: x.tag_type == TAG_TYPE.CITY, tags))

    for t in tags:
        try:

            if t.tag_type in (TAG_TYPE.COUNTRY, TAG_TYPE.PROVINCE):
                continue

            if t.tag_type == TAG_TYPE.CITY:
                has_geo_tag = True

            DiaryTag.create_bind(diary_id=diary.id, tag_id=t.id)

        except:
            logging_exception()
            info_logger.info('tag_id: {} not found'.format(t.id))

    if not has_geo_tag:
        user_city_tagid = user.city_tag_id
        if user_city_tagid:
            try:
                DiaryTag.create_bind(diary_id=diary.id, tag_id=user_city_tagid)
            except:
                logging_exception()

    # 关于日记本标题的改动
    if not title:
        title = get_diary_title(tags) if tags else None
    if title:
        diary.title = title

    # 评分 兼容老接口
    try:
        rating = int(rating)
    except:
        rating = None

    if rating and 1 <= rating <= 5:
        diary.rating = rating

    record_rating = True
    for _rating in (operation_effect_rating, doctor_attitude_rating, hospital_env_rating):
        if _rating < 1 or _rating > 5:
            record_rating = False
            break

    if record_rating:
        # 如果有非法的星级评价 此次作废
        # 如果comment和星级评价都传过来 以星级评价为准
        diary.operation_effect_rating = operation_effect_rating
        diary.doctor_attitude_rating = doctor_attitude_rating
        diary.hospital_env_rating = hospital_env_rating

    if cover:
        diary.pre_operation_image = cover

    # 添加术前图 改成批量
    pre_operation_images_list = []
    if pre_operation_images:
        taken_time = datetime.datetime.now()

        for image in pre_operation_images:
            if not image.get('image'):
                continue

            is_cover = False if image['image'] != cover else True
            pre_operation_images_list.append(PreOperationImage(
                diary=diary,
                image_url=image['image'],
                taken_time=taken_time,
                is_cover=is_cover,
                cover_image_url=image.get('modified_image_url')))

        PreOperationImage.objects.bulk_create(pre_operation_images_list)
        # 记录操作 UPDATE_INFO
        diary.set_diary_operate(DIARY_OPERATE.UPDATE_INFO)

    diary.is_online = False if create_for_draft else True
    diary.save()

    # 更新日记本封面图
    diary.cache_cover()

    return diary


def update_diary_operation_info_func(
        user,
        diary,
        title='',
        tag_ids=[],
        doctor_id=None,
        doctor_name=None,
        order_id=None,
        hospital_id=None,
        hospital_name=None,
        operation_timestamp=None,
        pre_cover='',
        post_cover='',
        only_update_order_info=False
):
    """
    更新日记本信息操作。 客户端会传递所有字段,所以这里做一次比较
    :param user:
    :param diary:
    :param title: 日记本标题
    :param tag_ids:
    :param doctor_id:
    :param doctor_name:
    :param order_id:
    :param hospital_id:
    :param hospital_name:
    :param operation_timestamp:
    :param pre_cover: 术前封面图,已经是处理过的图片地址
    :param post_cover:术后封面图,已经是处理过的图片地址
    :param only_update_order_info:仅更新绑定订单的数据
    :return:
    """
    diary_title_update_status = False  # 日记本标题更新状态
    diary_pre_cover_update_status = False  # 日记本术前封面图更新状态
    diary_post_cover_update_status = False  # 日记本术后封面图更新状态
    diary_update_operation_time = False  # 日记本手术时间更新状态

    diary, tag_ids, update_order_info = _update_diary_operation_info(
        diary,
        tag_ids=tag_ids,
        order_id=order_id,
        doctor_id=doctor_id,
        doctor_name=doctor_name,
        hospital_id=hospital_id,
        hospital_name=hospital_name
    )

    # 更新日记本标签
    diary, diary_diff_add_tag_ids, diary_diff_remove_tag_ids = _update_diary_tags(user, diary, tag_ids, update_order_info)
    topic_ids = list(Problem.objects.filter(diary_id=diary.id, is_online=True).values_list('id', flat=True))
    # 日记本标签变动同步到帖子上
    async_diary_change_tags_to_topic.delay(
        topic_ids=topic_ids,
        add_tags=diary_diff_add_tag_ids,
        del_tags=diary_diff_remove_tag_ids
    )

    if not only_update_order_info:
        _old_diary_pre_cover = diary.pre_operation_image
        _old_diary_post_cover = diary.post_operation_image
        _old_diary_operation_time = diary.operation_time

        # 修改日记本治疗时间
        if operation_timestamp:
            _date = datetime.date.fromtimestamp(float(operation_timestamp))

            if not _old_diary_operation_time:  # 日记本无治疗时间
                diary_update_operation_time = True
            elif _old_diary_operation_time and _old_diary_operation_time.date() != _date:  # 更新治疗时间
                diary_update_operation_time = True

            if diary_update_operation_time:
                diary.operation_time = _date
                diary.is_fake_operation_time = False
        else:
            diary.operation_time = datetime.datetime.now()
            diary.is_fake_operation_time = True
            diary_update_operation_time = True

        # 修改日记本标题
        if not title:
            tags = TagService.get_tags_by_tag_ids(tag_ids)
            title = get_diary_title(tags) if tags else None
            if title:
                diary.title = title
                diary_title_update_status = True
        else:
            diary_old_title = diary.title
            if title != diary_old_title:
                diary.title = title
                diary_title_update_status = True

        # 修改术前、术后封面图
        if pre_cover and pre_cover != Picture.get_short_path(_old_diary_pre_cover):
            PreOperationImage.objects.filter(diary_id=diary.id, image_url=pre_cover).update(cover_image_url=pre_cover)
            set_diary_cover(pre_cover, diary)

            diary.pre_operation_image = pre_cover
            diary_pre_cover_update_status = True

        if post_cover and post_cover != Picture.get_short_path(_old_diary_post_cover):
            TopicImage.objects.filter(topic_id__in=topic_ids, image_url=post_cover).update(cover_image_url=post_cover)
            set_diary_postoperation_cover(post_cover, diary)

            diary.post_operation_image = post_cover
            diary_post_cover_update_status = True

    if any([
        diary_pre_cover_update_status,
        diary_post_cover_update_status,
        diary_title_update_status,
        diary_diff_add_tag_ids,
        diary_update_operation_time,
    ]):
        d = DiaryRankTool(diary.id)
        d.add_heat(DIARY_OPERATION.MODIFY_DIARY, datetime.datetime.now())

        if any([
            diary_pre_cover_update_status,
            diary_post_cover_update_status,
            diary_title_update_status,
            diary_diff_add_tag_ids,
        ]):
            # 记录操作 UPDATE_INFO
            diary.set_diary_operate(DIARY_OPERATE.UPDATE_INFO)

    # 更换封面图、或是更新日记本信息,都会更新日记本的更改时间
    diary.last_modified = datetime.datetime.now()
    diary.save()

    # 更新日记本封面图
    diary.cache_cover()

    return diary


def _update_diary_tags(user, diary, tag_ids, update_order_info=False):
    """
    更新日记本标签
    处理标签 由于存储的是 项目标签 + 运营标签 还需要对数量做限制!!!
    当前逻辑是仅处理 1、2、3级 标签的更新 or 删除。其他不管
    :param user:
    :param diary:
    :param tag_ids:
    :return:
    """
    # 日记本已有的标签
    diary_old_tag_ids = list(DiaryTag.objects.filter(diary=diary).values_list("tag_id", flat=True))
    diary_old_tag_v3_ids = list(DiaryTagV3.objects.filter(diary_id=diary.id).values_list("tag_v3_id", flat=True))

    # 如果日记本有视频标签,则移除视频标签不作为比较项
    if settings.VIDEO_TAG_ID in diary_old_tag_ids:
        diary_old_tag_ids.remove(settings.VIDEO_TAG_ID)

    if settings.VIDEO_TAG_V3_ID in diary_old_tag_v3_ids:
        diary_old_tag_v3_ids.remove(settings.VIDEO_TAG_V3_ID)

    diary_old_tags_obj_list = TagService.get_tags_by_tag_ids(diary_old_tag_ids)

    # 外部传入的标签
    _new_tag_obj_list = TagService.get_tags_by_tag_ids(list(set(tag_ids)))

    new_tag_obj_list = sorted(filter(lambda t: t.tag_type not in TAG_CITY_TYPE_LIST, _new_tag_obj_list),
                              key=lambda tag_item: tag_ids.index(tag_item.id))[:settings.DIARY_TAGS_MAX_COUNT]

    need_update_tag_ids = [tag.id for tag in new_tag_obj_list]  # 最终需要更新的标签

    # 关于城市标签的处理

    # 在日记本已绑定的标签中 筛选 城市标签
    diary_exist_city_tag_list = list(filter(lambda x: x.tag_type == TAG_TYPE.CITY, diary_old_tags_obj_list))

    # 查询外部传入的标签中,城市类型的标签
    new_city_tag_obj_list = list(filter(lambda x: x.tag_type == TAG_TYPE.CITY, _new_tag_obj_list))

    # 可以更新地域标签的状态
    need_update_diary_city_tag = update_order_info or (not update_order_info and not diary_exist_city_tag_list)

    if need_update_diary_city_tag:
        # 城市标签补齐策略
        _city_tag_obj_list = new_city_tag_obj_list or diary_exist_city_tag_list
        if _city_tag_obj_list:
            tag = _city_tag_obj_list[0]
            need_update_tag_ids.append(tag.id)
        else:
            if user.city_tag_id:
                need_update_tag_ids.append(user.city_tag_id)

    #标签的替换 获取日记本的1、2、3级标签, 若更新订单信息,需要把地域标签替换
    if need_update_diary_city_tag:
        TAG_ITEM_TYPE_LIST.extend([TAG_TYPE.CITY, ])
    diary_old_item_tag_ids = [tag.id for tag in diary_old_tags_obj_list if tag.tag_type in TAG_ITEM_TYPE_LIST]

    # 比较1、2、3级标签进行处理 or add city tag
    diary.add_tags(need_update_tag_ids)
    diary.del_tags(list(set(diary_old_item_tag_ids) - set(need_update_tag_ids)))

    # 计算日记本tag修改后新增和删除的tag,注意这些数据需要同步到帖子上!
    diary_diff_add_tag_ids = set(need_update_tag_ids) - set(diary_old_item_tag_ids)
    diary_diff_remove_tag_ids = set(diary_old_item_tag_ids) - set(need_update_tag_ids)

    return diary, list(diary_diff_add_tag_ids), list(diary_diff_remove_tag_ids)


def _update_diary_operation_info(
        diary,
        order_id=None,
        tag_ids=[],
        doctor_id=None,
        doctor_name=None,
        hospital_id=None,
        hospital_name=None
):
    """
    更新日记本的手术信息
    :param diary:
    :param order_id:
    :param tag_ids:
    :param doctor_id:
    :param doctor_name:
    :param hospital_id:
    :param hospital_name:
    :return:
    """
    # 如果有订单,按照订单的信息去更新,没有按照用户选择的去更新
    update_order_info = False

    if order_id:
        order_info = OrderService.get_order_data_for_update_diary_operation_info(order_id=order_id)
    else:
        order_info = None

    if order_info:
        diary.order_id = order_id

        diary.doctor_id = order_info['doctor_id']
        diary.raw_doctor = order_info['doctor_name']

        diary.hospital_id = order_info['hospital_id']
        diary.raw_hospital = order_info['hospital_name']

        diary.service_id = order_info['service_id']

        # 评分
        diary.rating = order_info.get("rating", 0)
        diary.operation_effect_rating = order_info.get("operation_effect", 0)
        diary.doctor_attitude_rating = order_info.get("doctor_attitude", 0)
        diary.hospital_env_rating = order_info.get("hospital_env", 0)

        tag_ids.append(order_info["city_tag_id"])
        update_order_info = True
    else:
        if doctor_id:
            diary.doctor_id = doctor_id
        else:
            diary.doctor_id = None

        if doctor_name:
            diary.raw_doctor = doctor_name

        if hospital_id:
            diary.hospital_id = hospital_id
        else:
            diary.hospital_id = None

        if hospital_name:
            diary.raw_hospital = hospital_name

    return diary, tag_ids, update_order_info


def create_diary(diary_list, services_tags):
    """
    创建日记本 关联美购
    :param diary_list:
    :param services_tags:
    :return:
    """
    def get_random_time(base_time):  # 生成随机时间
        start = datetime.datetime.fromtimestamp(
            time.mktime(time.strptime(base_time, '%Y-%m-%d %H:%M:%S'))
        )
        delta = datetime.datetime.now() - start
        inc = random.randrange(int(delta.total_seconds()))
        return start + datetime.timedelta(seconds=inc)

    topic_obj_list = []
    with transaction.atomic():
        for diary_dict in diary_list:
            service_id = diary_dict.get('service_id')
            start_time = diary_dict.get('start_time')
            operation_time = get_random_time(start_time) if start_time else datetime.datetime.now()

            # 美购关联的标签
            tag = services_tags.get(str(service_id))
            title = (tag + '日记') if tag else ''

            diary_info = {
                "user_id": diary_dict.get('user_id'),
                "title": title,
                "created_time": operation_time,
                "last_modified": operation_time,
                "last_topic_add_time": operation_time,
                "operation_time": operation_time,
                "is_fake_operation_time": True,
                "doctor_id": diary_dict.get('doctor_id'),
                "raw_doctor": diary_dict.get('doctor_name'),
                "raw_hospital": diary_dict.get('hospital_name'),
                "hospital_id": diary_dict.get('hospital_id'),
                "is_import": True,
                "service_id": service_id
            }

            try:
                diary = Diary.objects.create(**diary_info)
                DiaryExtra.objects.create(diary=diary)

                topic_dict = {
                    "answer": diary_dict.get('content'),
                    "diary_id": diary.id,
                    "service_id": service_id,
                    "link_doctor_id": diary_dict.get('doctor_id'),
                    "raw_doctor": diary_dict.get('doctor_name'),
                    "link_hospital_id": diary_dict.get('hospital_id'),
                    "raw_hospital": diary_dict.get('hospital_name'),
                    "user_id": diary_dict.get('user_id'),
                    "topic_type": TOPIC_TYPE.SHARE,
                    "operation_date": datetime.datetime.now(),
                }
                topic_obj_list.append(Problem(**topic_dict))
            except:
                raise Exception("美购:{}创建日记本失败".format(service_id))

        Problem.objects.bulk_create(topic_obj_list)

    return