# coding=utf-8
from __future__ import unicode_literals, absolute_import
import json
from datetime import datetime
from django.conf import settings
from django.db import IntegrityError, transaction
from django.db.models import Q
from api.models import (Coupon, CouponLaunch, CouponInfo, CouponGift,
                        GiftToCoupon, CouponDoctorRestrict, CouponSpecialRestrict, ChannelGift, BusinessChannel,
                        ChannelGiftStatistics)
from api.models import GiftChannelLaunch, ChannelGiftUserRelation
from hera.queries.coupon import CouponDQ
from api.tool.user_tool import get_user_from_context
from hera.datatables import CouponDT, CouponInfoDT
from hera.queries.coupon import CouponGiftDQ, ChannelDQ, ChannelGiftDQ
from hera.models import Config
from rpc.cache import coupon_cache
from rpc.decorators import bind_context
from rpc.exceptions import RPCIntegrityError, RPCNotFoundException, GaiaRPCFaultException
from rpc.tool.dict_mixin import to_dict
from rpc.tool.error_code import gen, CODES
from rpc.tool.generate_random_code import code_generator
from rpc.tool.log_tool import info_logger
from ..utils import get_choices
from ..queries.usercoupon import UserCouponDQ
from gm_types.gaia import COUPON_TYPES, COUPON_GIFT_TYPES, COUPON_DISTRIBUTION_STATUS, BENEFIT_TYPE
from gm_types.doctor import COUPON_FILTER_STATUS
from gm_types.error import ERROR
from hera.queries.couponrestric import CouponSKUDQ

__author__ = 'leaf'


@bind_context('hera/coupon/choices')
def coupon_choices(ctx, q='', page=1, num=30, initial=None):
    src_map = {'id': 'id', 'text': 'name'}
    if initial is None:
        qry = Q(id__contains=q) | Q(name__contains=q)
    else:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    query = Coupon.objects.filter(distribution_status=COUPON_DISTRIBUTION_STATUS.OPEN).filter(qry)

    total_count = 0
    start_pos = (page - 1) * num
    start_pos = start_pos if start_pos >= 0 else 0
    results = [
        {
            'id': obj.id,
            'text': u'{}({})'.format(obj.name, COUPON_TYPES.getDesc(obj.coupon_type)),
        } for obj in query[start_pos: start_pos + num]
        ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('hera/coupon/create', login_required=True)
def coupon_create(ctx, coupon_info):
    """
    新建美券
    :param ctx:
    :desc name 美券名称  value 美券价值  total 总张数  start_time 起始时间  end_time 结束时间  announcement 特殊说明
    :param coupon_info: {"name": "test", "value": 10, "total": 10, "start_time": "2015-07-02",
                         "end_time": "2015-07-10", "announcement": ""}
    :return: coupon_id
    """
    coupon_info['doctor_id'] = coupon_info.pop('doctor') or None
    try:
        # end_time = coupon_info['end_time'][:10]+' 23:59:59'
        # coupon_info['end_time'] = end_time
        coupon = Coupon.objects.create(**coupon_info)
    except IntegrityError:
        info_logger.info(__import__('traceback').format_exc())
        raise RPCIntegrityError
    return {'coupon_id': coupon.id}


@bind_context('hera/coupon/update', login_required=True)
def coupon_update(ctx, coupon_id, coupon_info=None):
    """
    更新美券
    :param ctx:
    :param coupon_id:
    :param coupon_info: {"name": "test", "value": 10, "total": 10, "start_time": "2015-07-02",
                         "end_time": "2015-07-10", "announcement": ""}
    :return:
    """
    if coupon_info is None:
        return None
    else:
        coupon = Coupon.objects.filter(id=coupon_id)
        if coupon.count() != 1:
            return gen(CODES.COUPON_NOT_FOUND)
        # coupon_info['end_time'] = coupon_info['end_time'][:10]+' 23:59:59'
        coupon.update(**coupon_info)
    return {'coupon_id': coupon[0].id}


@transaction.atomic
@bind_context('hera/coupon/edit', login_required=True)
def coupon_edit(ctx, coupon_info, coupon_id=None):
    """
    编辑coupon信息
    """
    if coupon_info is None:
        return None
    doctors = coupon_info.pop('doctors')
    specials = coupon_info.pop('specials')

    if coupon_id is None:
        try:
            coupon = Coupon.objects.create(**coupon_info)
        except Exception as e:
            raise RPCIntegrityError
    else:
        try:
            coupon = Coupon.objects.get(id=coupon_id)
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise RPCNotFoundException
        for k, v in coupon_info.iteritems():
            setattr(coupon, k, v)
        coupon.save()

    old_coupon = set(CouponDoctorRestrict.objects.filter(coupon__id=coupon.id))
    new_coupon = set(CouponDoctorRestrict.objects.get_or_create(coupon_id=coupon.id,
                                                                doctor_id=doctor
                                                                )[0] for doctor in doctors)
    for item in (old_coupon - new_coupon):
        item.delete()

    old_special = set(CouponSpecialRestrict.objects.filter(coupon__id=coupon.id))
    new_special = set(CouponSpecialRestrict.objects.get_or_create(coupon_id=coupon.id,
                                                                  special_id=special
                                                                  )[0] for special in specials)
    for item in (old_special - new_special):
        item.delete()
    return {"coupon_id": coupon.id}


@bind_context('hera/coupon/query')
def coupon_query(ctx, options):
    dqobj = CouponDQ()
    return dqobj.process(**options)


@bind_context('hera/coupon/create_launch', login_required=True)
def coupon_launch_create(ctx, channel, total, coupon_id):
    """
    投放美券(生成投放记录时，需要对应的生成投放数量的优惠券)
    :param ctx:
    :param channel: 投放渠道
    :param total: 投放总数
    :param coupon_id:
    :return:
    """
    assert isinstance(total, int)
    user = get_user_from_context(ctx)
    launch_total = total
    try:
        coupon = Coupon.objects.get(pk=coupon_id)
        if total > coupon.total:
            return gen(CODES.COUPON_TOTAL_INVALID)
        totals = CouponLaunch.objects.filter(coupon=coupon).values('total')
        for i in totals:
            launch_total += i['total']
        if launch_total > coupon.total:
            return gen(CODES.COUPON_TOTAL_INVALID)
    except Coupon.DoesNotExist:
        return gen(CODES.COUPON_NOT_FOUND)
    coupon_launch = CouponLaunch()
    coupon_launch.coupon = coupon
    coupon_launch.channel = channel
    coupon_launch.total = total
    coupon_launch.operator = user
    coupon_launch.save()
    for i in range(total):
        code = code_generator()
        CouponInfo.objects.create(coupon=coupon, code=code, value=coupon.value, coupon_launch=coupon_launch,
                                  channel=channel)
    return {"launch_id": coupon_launch.id}


@bind_context('hera/coupon/list', login_required=True)
def get_coupon_list(ctx, req_data):
    """
    美券列表
    :param ctx:
    :param req_data:
    :return: coupon_data
    """
    coupon_obj = CouponDT(Coupon)
    coupon_data = coupon_obj.process(req_data, ['name'])
    return coupon_data


@bind_context('hera/coupon/get', login_required=True)
def get_coupon_detail(ctx, coupon_id, options=None):
    """
    美券详情
    :param ctx:
    :param coupon_id:
    :return: coupon_data
    """
    try:
        coupon = Coupon.objects.get(id=coupon_id, coupon_type=COUPON_TYPES.PLATFORM)
    except Coupon.DoesNotExist:
        return gen(CODES.COUPON_NOT_FOUND)
    if options is None:
        options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    coupon_data = to_dict(coupon, **options)
    coupon_data['campaign_id'] = coupon_data['campaign']
    doctors = CouponDoctorRestrict.objects.filter(coupon_id=coupon_id).values_list('doctor__id', flat=True)
    specials = CouponSpecialRestrict.objects.filter(coupon_id=coupon_id).values_list('special__id', flat=True)
    coupon_data['doctors'] = list(doctors)
    coupon_data['specials'] = list(specials)

    return coupon_data


# @bind_context('hera/coupon/list_launch', login_required=True)
# def get_coupon_launch_list(ctx, coupon_id, options=None):
#     """
#     获取美券投放列表
#     :param ctx:
#     :param coupon_id:
#     :return: left_amount 美券可投放数量
#              launch_data
#     """
#     try:
#         coupon = Coupon.objects.get(id=coupon_id)
#     except Coupon.DoesNotExist:
#         return gen(CODES.COUPON_NOT_FOUND)
#
#     coupon_launch_list = CouponLaunch.objects.filter(coupon=coupon)
#     launch_total = 0
#     for i in coupon_launch_list:
#         launch_total += i.total
#     if options is None:
#         options = {
#             'fields': ['id', 'channel_name', 'get_num', 'total', 'launch_time', 'operator_id'],
#             'excludes': None,
#             'expands': None,
#         }
#     launch_data = [to_dict(coupon_launch, **options) for coupon_launch in coupon_launch_list]
#     return {"left_amount": coupon.total-launch_total, "launch_data": launch_data, "coupon_name": coupon.name}


@bind_context('hera/coupon/list_launch', login_required=True)
def get_coupon_launch_list(ctx, coupon_id, options=None):
    """
    获取美券投放列表
    :param ctx:
    :param coupon_id:
    :return: left_amount 美券可投放数量
             launch_data
    """
    try:
        coupon = Coupon.objects.get(id=coupon_id)
    except Coupon.DoesNotExist:
        return gen(CODES.COUPON_NOT_FOUND)

    coupon_launch_list = CouponLaunch.objects.filter(coupon=coupon)
    launch_total = 0
    for i in coupon_launch_list:
        launch_total += i.total
    if options is None:
        options = {
            'fields': ['id', 'channel_name', 'get_num', 'total', 'launch_time', 'operator_id'],
            'excludes': None,
            'expands': None,
        }
    launch_data = [to_dict(coupon_launch, **options) for coupon_launch in coupon_launch_list]
    return {"left_amount": coupon.total - launch_total, "launch_data": launch_data, "coupon_name": coupon.name}


@bind_context('hera/coupon/list_info_codes', login_required=True)
def get_coupon_info_code_list(ctx, launch_id):
    """
    获取投放的所有优惠券id
    :param ctx:
    :param launch_id:
    :return: coupon_info_codes 优惠券id列表

    """
    try:
        coupon_launch = CouponLaunch.objects.get(id=launch_id)
    except CouponLaunch.DoesNotExist:
        return gen(CODES.COUPON_LAUNCH_TOTAL_INVALID)

    coupon_info_list = CouponInfo.objects.filter(coupon_launch=coupon_launch).values('code')
    code_list = [i['code'] for i in coupon_info_list]

    return {"coupon_info_ids": code_list}


@bind_context('hera/coupon/list_gift', login_required=True)
def gift_list(ctx, options):
    return CouponGiftDQ().process(**options)


@bind_context('hera/coupon/gift_choices', login_required=True)
def gift_choice(ctx, q='', page=1, num=30, initial=None):
    src_map = {'id': 'id', 'text': 'name'}
    if initial is None:
        qry = Q(id__contains=q) | Q(name__contains=q)
    else:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    query = CouponGift.objects.filter(qry)
    return get_choices(query, page, num, src_map)


@bind_context('hera/coupon/channel_gift_choices')
def channel_gift_choice(ctx, q='', page=1, num=30, channel_id=None, initial=None):
    page = int(page)
    num = int(num)
    pos = (page - 1) * num
    pos = pos if pos >= 0 else 0
    if initial is None:
        qry = Q(gift__id__contains=q) | Q(gift__name__contains=q)
    else:
        qry = Q(gift__id=initial)
    q = ChannelGift.objects.filter(gift_enable=True).filter(qry)
    if channel_id:
        q = q.filter(business_channel_id=channel_id)
    total_count = q.count()
    results = [dict(id=x.gift.id, text=x.gift.name) for x in q[pos:pos + num]]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}


@bind_context('hera/coupon/channel', login_required=True)
def channel_list(ctx, options):
    return ChannelDQ().process(**options)


@bind_context('hera/coupon/channel_gift', login_required=True)
def channel_gift_list(ctx, options):
    return ChannelGiftDQ().process(**options)


@bind_context('hera/coupon/channel_gift_edit', login_required=True)
def channel_gift_edit(ctx, channel_id, channel_gift_id, gift_id, total, limit, displayable):
    if channel_gift_id:
        try:
            channel_gift = ChannelGift.objects.get(id=channel_gift_id)
            gift = CouponGift.objects.get(id=gift_id)
        except ChannelGift.DoesNotExist:
            return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
        channel_gift.gift = gift
        channel_gift.total = total
        channel_gift.limit = limit
        channel_gift.displayable = displayable
        channel_gift.save()
    else:

        # if GiftToCoupon.objects.filter(
        #         coupon_gift_id=gift_id, coupon__benefit_type=BENEFIT_TYPE.DISCOUNT
        # ).exists():
        #     raise GaiaRPCFaultException(error=ERROR.UNIVERSAL, message=u'当前礼包含有折扣券类型美券，暂不可以保存', data=None)

        try:
            with transaction.atomic():
                channel = BusinessChannel.objects.get(id=channel_id)
                gift = CouponGift.objects.get(id=gift_id)
                channel_gift = ChannelGift.objects.create(
                    business_channel=channel, gift=gift, total=total, limit=limit,
                    displayable=displayable
                )
                ChannelGiftStatistics.objects.get_or_create(
                    channel_gift_id=channel_gift.id
                )
        except BusinessChannel.DoesNotExist, ChannelGift.DoesNotExist:
            return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
    return to_dict(channel_gift)


@bind_context('hera/coupon/channel_gift_launch', login_required=True)
def channel_gift_launch(ctx, channel_gift_id, amount):
    gift = ChannelGift.objects.get(id=channel_gift_id)
    left = ChannelGiftUserRelation.objects.filter(channel_gift=gift).count()
    if amount > gift.total - left:
        return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
    launch = GiftChannelLaunch.objects.create(
        total=amount, operator=ctx.session.user, channel_gift=gift)
    with transaction.atomic():
        create = ChannelGiftUserRelation.objects.create_with_code
        return [to_dict(create(gift, launch)) for _ in range(amount)]


@bind_context('hera/coupon/get_channel', login_required=True)
def channel_get(ctx, channel_id):
    try:
        channel = BusinessChannel.objects.get(id=channel_id)
    except BusinessChannel.DoesNotExist:
        return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
    return to_dict(channel)


@bind_context('hera/coupon/channel_edit', login_required=True)
def channel_edit(ctx, channel_id, desc, memo, activate):
    if channel_id is None:
        channel = BusinessChannel.append(desc, memo)
    else:
        try:
            channel = BusinessChannel.objects.get(id=channel_id)
        except BusinessChannel.DoesNotExist:
            return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
    if not channel.activated_time:
        channel.desc = desc
        channel.memo = memo
        channel.gift_enable = False

    if activate:
        if not channel.activated_time:
            channel.activated_time = datetime.now()
        ChannelGift.objects.filter(business_channel=channel) \
            .filter(activated_time__isnull=True) \
            .update(activated_time=datetime.now(), gift_enable=True)
    channel.save()
    return to_dict(channel)


@bind_context('hera/coupon/get_gift', login_required=True)
def gift_detail(ctx, gift_id, options=None):
    try:
        gift = CouponGift.objects.get(id=gift_id, gift_type=COUPON_GIFT_TYPES.PLATFORM)
    except CouponGift.DoesNotExist:
        return gen(CODES.COUPON_GIFT_DOES_NOT_EXIST)
    if options is None:
        options = {
            'fields': None,
            'excludes': None,
            'expands': None,
        }
    gift_data = to_dict(gift, **options)
    gift_data['coupons'] = [to_dict(rel) for rel in GiftToCoupon.objects.filter(coupon_gift_id=gift_id)]
    for d in gift_data['coupons']:
        coupon_info=Coupon.objects.filter(id=d.get('coupon')).first()
        d['valid_time']=("用户领取后 {} 天内".format(coupon_info.countdown) if coupon_info.countdown else
                         "{}年{}月{}日 至 {}年{}月{}日".format(coupon_info.start_time.year, coupon_info.start_time.month,
                                                        coupon_info.start_time.day, coupon_info.end_time.year,
                                                        coupon_info.end_time.month, coupon_info.end_time.day))
    return gift_data


@bind_context('hera/coupon/coupon_valid_time', login_required=True)
def coupon_valid_time(ctx, coupon_id, options=None):
    coupon_info = Coupon.objects.filter(id=coupon_id).first()
    coupon_valid_time = ("用户领取后 {} 天内".format(coupon_info.countdown) if coupon_info.countdown else
                         "{}年{}月{}日 至 {}年{}月{}日".format(coupon_info.start_time.year, coupon_info.start_time.month,
                                                        coupon_info.start_time.day, coupon_info.end_time.year,
                                                        coupon_info.end_time.month, coupon_info.end_time.day))
    return coupon_valid_time


@bind_context('hera/coupon/edit_gift', login_required=True)
def gift_edit(ctx, gift_id=None, gift_info=None, coupons=None):
    """新建或更新大礼包

    :param gift_id: 礼包id
    :param gift_info: 礼包信息
    :param coupons: 礼包关联的coupon信息, (coupon_id, number)
    """
    if gift_info is None:
        return None

    if gift_id is None:
        try:
            gift_info['end_time'] = gift_info['end_time'] + " 23:59:59"
            gift = CouponGift.objects.create(
                operator=ctx.session.user, **gift_info)
        except IntegrityError as e:
            raise RPCIntegrityError
    else:
        try:
            gift = CouponGift.objects.get(id=gift_id)
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise RPCNotFoundException
        for k, v in gift_info.iteritems():
            setattr(gift, k, v)
    gift.operator = ctx.session.user
    gift.save()

    if coupons is not None:
        is_changed = False  # 是否有变化, 即是否需要更新缓存数据
        new_coupons = []

        for cpinfo in coupons:
            if not all(cpinfo):
                # coupon_id 或 number 不存在，跳过
                continue
            gift_coupon, _created = GiftToCoupon.objects.get_or_create(
                coupon_gift_id=gift.id, coupon_id=cpinfo[0],
                defaults={'number': cpinfo[1]})
            if _created or str(gift_coupon.number) != str(cpinfo[1]):
                # GiftToCoupon 新建或者number有更新, 需要更新缓存
                is_changed = True
                gift_coupon.number = cpinfo[1]
                gift_coupon.save()
            new_coupons.append(gift_coupon)

        old_coupons = GiftToCoupon.objects.filter(coupon_gift_id=gift.id)
        x_coupons = set(old_coupons) - set(new_coupons)
        if x_coupons:
            # GiftToCoupon 有减少, 需要更新缓存
            is_changed = True
            GiftToCoupon.objects.filter(id__in=[o.id for o in x_coupons]).delete()

        gift.value = sum([int(c.coupon.value) * int(c.number) for c in new_coupons])
        gift.save()
        """
        @deprecated
        if is_changed:
            # 大礼包关联美券有改动，触发清空当前礼包缓存并重新生成
            clear_gift(gift.id)
            generate_gift(gift.id)
        """
    return gift.id


@bind_context('hera/coupon/query_info', login_required=True)
def coupon_info_query(ctx, options):
    dqobj = UserCouponDQ()
    return dqobj.process(**options)


@bind_context('hera/coupon/info', login_required=True)
def coupon_info(ctx, req_data):
    dtobj = CouponInfoDT(CouponInfo)
    return dtobj.process(req_data)


@bind_context('hera/coupon/get_doctor_gift')
def doctor_coupon_info(ctx, coupon_id):
    """
        医生美券详情页
    """
    try:
        coupon = Coupon.objects.get(id=coupon_id, coupon_type=COUPON_TYPES.DOCTOR)
    except:
        raise RPCNotFoundException
    coupon_info = coupon.detail_for_doctor()
    coupon_info['sku_count'] = len(coupon_info['skus'])
    coupon_info['filter_status'] = COUPON_FILTER_STATUS.getDesc(coupon_info['filter_status'])
    return coupon_info


@bind_context('hera/coupon/doctor_sku_query')
def coupon_info_query(ctx, options):
    dqobj = CouponSKUDQ()
    return dqobj.process(**options)


@bind_context('hera/coupon/doctor_offline')
def doctor_coupon_offline(ctx, coupon_id):
    try:
        coupon = Coupon.objects.get(id=coupon_id)
    except:
        raise RPCNotFoundException
    if coupon.distribution_status != COUPON_DISTRIBUTION_STATUS.OPEN:
        return gen(CODES.DATA_SOURCE_NOT_CORRECT)
    Coupon.objects.close_doctor_coupon(coupon)


@bind_context('hera/coupon/edit/global_channelgift_ids')
def batch_add_channelgift_ids(ctx, channelgift_ids):
    """
    批量添加渠道礼包ID, 先存DB,在存redis
    :param ctx:
    :param channelgift_ids:
    :return:
    """
    ret = {
        'code': 0,
        'message': '保存成功',
        'id': ''
    }
    obj, is_created = Config.objects.update_or_create(key="global_gift_ids", defaults={"value": json.dumps(channelgift_ids)})

    try:
        gift_id_list = [ChannelGift.objects.get(pk=chan_id).gift_id for chan_id in channelgift_ids]
    except ChannelGift.DoesNotExist:
        ret['code'] = 1,
        ret['message'] = '保存失败'
        return ret

    coupon_cache.set("global_gift_ids", json.dumps(gift_id_list))
    ret['id'] = obj.id
    return ret


@bind_context('hera/coupon/get/global_channelgift_ids')
def batch_add_channelgift_ids(ctx, channelgift_id):
    """
    获取渠道礼包
    :param ctx:
    :param channelgift_ids:
    :return:
    """
    global_channelgift_ids = Config.objects.get(key="global_gift_ids").value
    if global_channelgift_ids:
        global_channelgift_ids = json.loads(global_channelgift_ids)

    filter_Q= Q(activated_time__isnull=False) & \
         Q(gift_enable=True) & \
         Q(displayable=True) & \
         Q(gift__end_time__gte=datetime.now()) & \
         Q(gift__gift_type=COUPON_GIFT_TYPES.PLATFORM)
    valid_channelgift_ids_list = ChannelGift.objects.filter(filter_Q, id__in=global_channelgift_ids).values_list('id', flat=True)

    return {
        'channelgift': list(valid_channelgift_ids_list),
    }


@bind_context('hera/coupon/global_channelgift/choices')
def global_channelgift(ctx, q='', page=1, num=30, initial=None):

    init_q = Q(activated_time__isnull=False) & \
             Q(gift_enable=True) & \
             Q(displayable=True) & \
             Q(gift__end_time__gte=datetime.now()) & \
             Q(gift__gift_type=COUPON_GIFT_TYPES.PLATFORM)

    if initial is None:
        qry = init_q
    else:
        if isinstance(initial, (list, tuple)):
            qry = Q(id__in=initial)
        else:
            qry = Q(id=initial)
    query = ChannelGift.objects.filter(qry)

    total_count = 0
    start_pos = (page - 1) * num
    start_pos = start_pos if start_pos >= 0 else 0
    results = [
        {
            'id': obj.id,
            'text': u'{}(礼包id:{}-渠道名称:{}-礼包名称:{}-结束时间:{})'.format(
                obj.id,
                obj.gift_id,
                obj.business_channel.desc,
                obj.gift.name,
                obj.gift.end_time
            ),
        } for obj in query[start_pos: start_pos + num]
        ]
    return {'total_count': total_count, 'results': results, 'page': page, 'num': num}