# coding:utf8

import math
import random
import datetime
import json

from django.db import models, transaction
from django.db.models import Q, F
from django.db.models import Max, Min
from django.utils import timezone
from django.conf import settings
from gm_types.error import ERROR
from rpc.exceptions import GaiaRPCFaultException

from gm_protocol import GmProtocol
from gm_types.gaia import SECKILL_TYPE, SLIDE_TYPE, RANGE_TYPE, BUTTON_SELECTION, POLYMER_TYPE
from rpc.tool.log_tool import info_logger
from .share import Share
from .doctor import Doctor
from .service import Service
from .service import ServiceItem
from .types import PROBLEM_FLAG_CHOICES
from .person import Person
from api.basemodel import RequestSourceModel
from api.models import Tag, ServiceItemPrice, SKUPriceRule, ServiceItem
from .area import Region, City
from .itemwiki import ItemWiki
from talos.models.topic import Problem

import rpc.db_opt
# Enumeration is deprecated, reference: http://git.gengmei.cc/backend/gm-types
# from rpc.tool.enumeration import Enumeration
from gm_types.gaia import (
    SPECIAL_TYPE,
    SPECIAL_STYLE_TYPE,
    THEME_TYPE,
    SERVICE_HOME_SECKILL_STATUS,
    SLIDE_TYPE,
    DOCTOR_SECKILL_APPLY_STATUS,
    SPECIAL_MODULE,
    ACTIVITY_TYPE_ENUM,
    SPECIAL_EXTRA_POSITION,
    DOCTOR_TYPE,
    DOCTOR_SECKILL_APPLY_TYPE,
    SLIDE_USER_TYPE,
    SLIDE_PAYMENT_TYPE,
    GROUPBUY_TYPE,
    ACTIVE_TYPE,
    SECKILL_POLYMER_MODULES
)
from api.tool.datetime_tool import get_timestamp, get_timestamp_epoch
from api.tool.log_tool import logging_exception
from gm_upload import ImgUrlField, IMG_TYPE

#专题
class Special(models.Model):
    class Meta:
        verbose_name = u'专题'
        verbose_name_plural = u'专题'
        app_label = 'api'
        index_together = [
            ['is_online', 'is_seckill', 'is_recommendpool', 'start_time'],
        ]

    LENGTH_TABLE = {
        SPECIAL_TYPE.SERVICE: {SPECIAL_STYLE_TYPE.FIRST: 3, SPECIAL_STYLE_TYPE.SECOND: 0},
        SPECIAL_TYPE.TOPIC: {SPECIAL_STYLE_TYPE.FIRST: 3, SPECIAL_STYLE_TYPE.SECOND: 2},
        SPECIAL_TYPE.DOCTOR: {SPECIAL_STYLE_TYPE.FIRST: 3, SPECIAL_STYLE_TYPE.SECOND: 3},
        SPECIAL_TYPE.USERCASE: {SPECIAL_STYLE_TYPE.FIRST: 3, SPECIAL_STYLE_TYPE.SECOND: 3},
    }
    SPECIAL_TYPE = SPECIAL_TYPE

    title = models.CharField(max_length=100, null=False, verbose_name=u'名称')
    desc = models.CharField(max_length=100, verbose_name=u'描述', help_text=u'列表页描述文字（小于50字）',
                            blank=True, default='')
    content = models.TextField(max_length=500, verbose_name=u'详情', help_text=u'专题详情页内容（少于500字）',
                               blank=True, default=u'')
    type = models.CharField(max_length=1, verbose_name=u'专题类型', null=False, blank=False,
                            default=SPECIAL_TYPE.TOPIC, choices=SPECIAL_TYPE, db_index=True)
    style_type = models.CharField(max_length=1, verbose_name=u'样式类型', default=SPECIAL_STYLE_TYPE.FIRST,
                                  choices=SPECIAL_STYLE_TYPE)
    theme = models.CharField(max_length=1, verbose_name=u"专题主题", default=THEME_TYPE.A, choices=THEME_TYPE)
    start_time = models.DateTimeField(verbose_name=u'起始时间', default=timezone.now)
    end_time = models.DateTimeField(verbose_name=u'结束时间', null=True, blank=True)
    doctor_name = models.CharField(verbose_name=u'医生姓名', help_text=u'仅在名医闪购当中需要', max_length=32, null=True,
                                   blank=True)
    hospital_name = models.CharField(verbose_name=u'医院名称', help_text=u'仅在名医闪购当中需要', max_length=128, null=True,
                                     blank=True)
    price_1 = models.CharField(verbose_name=u'价格1', max_length=16, default='', blank=True)
    price_2 = models.CharField(verbose_name=u'价格2', max_length=16, default='', blank=True)
    coupons = models.CharField(verbose_name=u'美券直领', max_length=16, default='')
    is_show_coupons = models.BooleanField(default=True, help_text=u"是否显示美券直领", verbose_name=u"显示美券")
    is_show_content = models.BooleanField(default=True, help_text=u"是否显示详情", verbose_name=u"显示详情")
    is_show_gadget = models.BooleanField(default=True, help_text=u"是否显示豆腐块", verbose_name=u"显示豆腐块")
    is_online = models.BooleanField(default=True, help_text=u"是否可以上线", verbose_name=u"上线", db_index=True)
    rank = models.IntegerField(default=0, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")
    image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, max_length=300, help_text=u'专题详情顶部图片', verbose_name=u'专题图片地址',
                        default=u'',
                        blank=True)
    icon = ImgUrlField(img_type=IMG_TYPE.SPECIAL, max_length=300, help_text=u'专题小图icon', verbose_name=u'专题ICON',
                       default='',
                       blank=True)
    recommend = models.BooleanField(verbose_name=u'置顶', default=False)
    is_seckill = models.BooleanField(verbose_name=u'秒杀专题', default=False)
    is_overflow = models.BooleanField(default=False, help_text=u"超值专题", verbose_name=u"超值")
    campaign = models.ForeignKey('Campaign', related_name='specials', default=None, null=True, blank=True,
                                 verbose_name=u'活动')
    purchase_limit = models.BooleanField(verbose_name=u'限购数量', default=True)
    is_servicehome = models.BooleanField(default=False, help_text=u"是否美购首页显示", verbose_name=u"是否美购首页显示")
    is_recommendpool = models.BooleanField(default=True,
                                           help_text=u"是否加入其他热卖推荐池",
                                           verbose_name=u'是否加入其他热卖推荐池')
    is_advertisement = models.BooleanField(u'是否是广告', default=False)
    # 分享文案相关
    share = models.ForeignKey(Share, default=None, verbose_name=u"分享文案", null=True)

    is_show_service_zone = models.BooleanField(default=False, verbose_name=u'显示美购分区')
    service_zone_content = models.TextField(verbose_name=u'美购分区内容', null=True, blank=True)
    tags = models.ManyToManyField(Tag, verbose_name=u'专题关联tag', db_table='api_special_rule')
    services = models.ManyToManyField(Service, db_table='api_specialservice_dbmw')
    # 秒杀专场相关
    seckill_type = models.CharField(u'秒杀类型', max_length=2, choices=SECKILL_TYPE, default=SECKILL_TYPE.GENGMEI)
    is_show_resource = models.BooleanField(u'是否显示秒杀资源位', default=True)
    rank_position = models.CharField(verbose_name=u'专题排序', max_length=256, default='', blank=True)
    xiaochengxu_img = ImgUrlField(img_type=IMG_TYPE.SHARE, verbose_name=u'小程序图片', max_length=300, default='')
    polymer_backgrond_image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, verbose_name=u'背景切换图(选中图)', max_length=300, default='')
    polymer_unselected_image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, verbose_name=u'背景切换图(未选中图)', max_length=300, default='')
    activity_rules = models.TextField(verbose_name=u'活动规则', null=True, blank=True)
    # 关联拼团
    groupbuy = models.OneToOneField("GroupBuySpecial", null=True)

    # 价格竞争力2期
    promotion_image = ImgUrlField(u'大促图片', img_type=IMG_TYPE.SPECIAL, max_length=255,
                                  help_text=u'美购详情价格底图', default='', blank=True)

    # 7765 http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=11607154
    is_new_special = models.BooleanField(u'是否为新专题', default=False)
    is_show_address = models.BooleanField(u'是否显示专题城市地址', default=False)

    # 双十一 多买
    is_more_buy = models.BooleanField(u'是否多买', default=False)
    more_buy_count = models.IntegerField(null=True, verbose_name=u'多买单品个数')

    commission_rate = models.IntegerField(verbose_name=u'活动抽佣比例', default=0)
    pre_payment_price_rate = models.IntegerField(verbose_name=u'预付款比例', default=0)

    @rpc.db_opt.prefetch_related(
        'items',
        # 'items__doctor',
        # 'items__topic',
        'items__service',
    )
    @rpc.db_opt.prefetch_nested(
        lambda: SpecialItem.special_items,
        prefix='items__'
    )
    def special_info(self, province_id=''):
        """
        获取客户端所需的Special信息，如果province_id参数存在，则把匹配的省份显示在最前
        :param province_id: (str) 省份ID
        :return: dict
        """
        if self.type == Special.SPECIAL_TYPE.DOCTOR:
            special_type = u'doctor'
            items = filter(lambda item: item.doctor and item.doctor.is_online, self.items.all())
        # todo 已不再维护 180314 *************start*************
        elif self.type == Special.SPECIAL_TYPE.TOPIC:
            special_type = u'topic'
            topic_ids = [t.topic_id for t in self.items.all()]
            item_ids = list(Problem.objects.filter(id__in=topic_ids, flag=PROBLEM_FLAG_CHOICES.NORMAL,
                                                   is_online=True).values_list('id', flat=True))

            items = filter(lambda t: t.topic_id in item_ids, self.items.all())
        # todo 已不再维护 180314  *************end*************
        else:
            items = filter(lambda item: item.service and item.service.is_online, self.items.all())
            special_type = u'service'

        data_items = [item.special_items(self.type) for item in sorted(items, key=lambda item: item.rank)]
        data_items = []

        if province_id:
            local_items = [item for item in data_items if item.get('province_id', '') == province_id]
            other_items = [item for item in data_items if item.get('province_id', '') != province_id]
            local_items.extend(other_items)
            data_items = local_items

        length = self.LENGTH_TABLE[self.type][self.style_type]
        data_items = data_items[:length]

        special_item = {
            'id': self.id,
            'title': self.title,
            'special_desc': self.desc,
            'icon': self.icon,
            'image': self.image,
            'type': special_type,
            'special_num': self.items.count(),
            'data': data_items,
            'style_type': self.style_type,
            'doctor_name': self.doctor_name,
            'hospital_name': self.hospital_name,
            'content': self.content,
            'price_1': self.price_1,
            'price_2': self.price_2,
            'end_timestamp': str(get_timestamp(self.end_time)) if self.end_time else '',
            'theme': self.theme,
            'is_seckill': self.is_seckill,
        }

        return special_item

    def get_share_data(self):
        if self.share:
            return {
                'share_title': self.share.title,
                'share_content': self.share.content,
                'share_moments': self.share.moments,
                'share_weibo': self.share.weibo,
                'share_image': self.share.image
            }
        else:
            return None

    def get_specail_type(self):
        '''获取专题类型'''
        special_type="common"
        if self.is_seckill:
            special_type="seckill"
        if self.groupbuy:
            special_type="spell"
        return special_type

    def get_groupbuy_type(self):
        '''获取团购类型'''
        groupbuy_type=""

        if self.groupbuy:
            if self.groupbuy.active_type==ACTIVE_TYPE.COMMON:
                groupbuy_type="common"
            elif self.groupbuy.active_type==ACTIVE_TYPE.WECHAT_ONE_YUAN:
                groupbuy_type="groupbuy_1"
        return groupbuy_type

    def data(self):
        coupongifts = []
        map_data = {
            Special.SPECIAL_TYPE.DOCTOR: "doctor",
            Special.SPECIAL_TYPE.TOPIC: "topic",
        }
        special_type = map_data.get(self.type, "service")
        coupons = SpecialLayout.layout_data(self.id, SPECIAL_MODULE.COUPONS) or {}
        has_sku_list = SpecialRelatedTag.objects.filter(special_id=self.id).exists() or \
                       SpecialItem.objects.filter(floor_id__isnull=False, special_id=self.id).exists()

        # 新专题美券都放在业务层处理
        # 旧专题获取美券数据
        if not self.is_new_special:
            coupongifts = [i['coupon_id'] for i in coupons['content']] if coupons else []

        _data = {
            'id': self.id,
            'title': self.title,
            'desc': self.desc,
            'icon': self.icon,
            'image': self.image,
            'type': special_type,
            'style_type': self.style_type,
            'doctor_name': self.doctor_name,
            'hospital_name': self.hospital_name,
            'rank_position': self.rank_position,
            'xiaochengxu_img': self.xiaochengxu_img,
            'content': self.content,
            'price_1': self.price_1,
            'price_2': self.price_2,
            'end_timestamp': str(get_timestamp(self.end_time)) if self.end_time else '',
            'start_timestamp': str(get_timestamp(self.start_time)) if self.start_time else '',
            'theme': self.theme,
            'is_seckill': self.is_seckill,
            'share_data': self.get_share_data(),
            'coupongifts': ','.join(coupongifts) if coupons.get('is_visible', False) else '',
            'service_zone': self.strip_service_zone() if self.is_show_service_zone else {},
            'is_show_coupons': coupons.get('is_visible', False),
            'is_show_content': self.is_show_content,
            'is_show_gadget': self.is_show_gadget,
            'is_show_service_zone': self.is_show_service_zone,
            'services_configured': self.items.filter(service__isnull=False).exists(),
            'multi_seckills_enter_list': self.get_resources()['seckills_list'],
            'is_groupbuy': False if self.groupbuy is None else True,
            'is_show_address': self.is_show_address,
            'is_new_special': self.is_new_special,
            'has_sku_list': has_sku_list, # 判断楼层是否含有美购列表
            "special_type": self.get_specail_type(),#专场类型
            "groupbuy_type": self.get_groupbuy_type()#团购类型
        }

        return _data

    def get_resources(self):
        res = {
            'seckills_list': [],
        }

        if not self.is_show_resource:
            return res

        resources = SpecialResource.objects.filter(
            special=self
        ).order_by('rank')

        for resource in resources:
            content = resource.data()['content']
            line = []
            for c in content:
                if c['jump_type'] in (SECKILL_RESOURCE_TYPE.CATEGORY, SECKILL_RESOURCE_TYPE.BRAND):
                    if c['jump_type'] == SECKILL_RESOURCE_TYPE.CATEGORY:
                        seckill_type = 1
                    else:
                        seckill_type = 2
                    data = {
                        'image': c['img'],
                        'id': '',
                        'url_info': seckill_type,  # param in url
                    }
                else:
                    data = {
                        'image': c['img'],
                        'id': c['jump_target'],
                        'url_info': c['jump_target'],
                    }

                line.append(data)

            res['seckills_list'].append(line)
        return res

    def strip_service_zone(self):
        result = {}
        try:
            related = json.loads(self.service_zone_content)
        except:
            return {}
        from api.view.service import get_service_multiget_zone
        result['banner'] = related[0]
        zones = related[1:]
        zone_list = []
        for zone_items in zones:
            tmp_zone = {}
            for zone_item in zone_items:
                if isinstance(zone_item, basestring):
                    tmp_zone['banner'] = zone_item
                    tmp_zone['lst'] = []
                elif isinstance(zone_item, list):
                    tmp_service = {}
                    tmp_service['tab'] = zone_item[0]
                    service_ids = zone_item[1:]
                    tmp_service['benefits'] = get_service_multiget_zone(service_ids)
                    tmp_zone['lst'].append(tmp_service)
            zone_list.append(tmp_zone)
        result['zones'] = zone_list
        return result

    def __unicode__(self):
        return self.title

    @property
    def sell_amount_display(self):
        items = self.items.filter(service__is_online=True).prefetch_related('service')
        sell_amount = 0
        for item in items:
            sell_amount += item.service.sell_amount_display
        return sell_amount

    @classmethod
    def get_newest_gengmei_seckill_info(cls):
        current_time = datetime.datetime.now()
        special = cls.objects.filter(
            is_seckill=True, is_online=True, end_time__gte=current_time, seckill_type=SECKILL_TYPE.GENGMEI
        ).only(
            'id', 'title', 'start_time', 'end_time', 'icon', 'image'
        ).order_by('end_time').first()
        if not special:
            return {}
        else:
            return special.get_gengmei_seckill_info()

    @classmethod
    def get_latest_n_special_ids(cls, n):
        current_time = datetime.datetime.now()
        special_ids = cls.objects.filter(
            is_seckill=True,
            is_online=True,
            end_time__gte=current_time,
            seckill_type=SECKILL_TYPE.GENGMEI
        ).values_list('id', flat=True).order_by('end_time')[:n]
        return special_ids

    @classmethod
    def get_gengmei_seckill_by_id(cls, special_id):
        try:
            special = cls.objects.only(
                'id', 'title', 'start_time', 'end_time', 'icon', 'image'
            ).get(id=special_id)
            return special.get_gengmei_seckill_info()
        except:
            return {}

    def get_gengmei_seckill_info(self):
        current_time = datetime.datetime.now()
        coupons = SpecialLayout.layout_data(self.id, SPECIAL_MODULE.COUPONS) or {}
        coupongifts = [i['coupon_id'] for i in coupons['content']] if coupons else []

        return {
            'id': self.id,
            "title": self.title,
            'start_timestamp': str(get_timestamp(self.start_time)) if self.start_time else '',
            'end_timestamp': str(get_timestamp(self.end_time)) if self.end_time else '',
            'status': SERVICE_HOME_SECKILL_STATUS.ACTIVE,
            'countdown': (self.end_time - current_time).total_seconds(),
            'icon': self.icon,
            'image': self.image,
            'coupongifts': ','.join(coupongifts) if coupons.get('is_visible', False) else '',
        }

    @classmethod
    def get_seckill_info(cls):
        q = Q(is_seckill=True) & Q(is_online=True)

        now = timezone.now()
        t = now + datetime.timedelta(days=settings.SECKILL_DAY)
        one_day_after = datetime.datetime(
            year=t.year,
            month=t.month,
            day=t.day,
            hour=23,
            minute=59,
            second=59
        )
        q &= Q(end_time__range=(now, one_day_after))

        sps = Special.objects.filter(q).order_by('start_time')
        for sp in sps:
            return cls.format_seckill_info(sp)

    @classmethod
    def format_seckill_info(cls, sp):
        """
        格式化秒杀数据
        :param sp:
        :return:
        """
        now = timezone.now()
        # check start < now & now <= end_time
        if sp.start_time <= now and now <= sp.end_time:
            return {
                'title': sp.title,
                'start_time': get_timestamp_epoch(sp.start_time),
                'end_time': get_timestamp_epoch(sp.end_time),
                # "status" and "countdown" is DEPRECATED as maybe cached
                # use "start_time" and "end_time" instead
                'status': SERVICE_HOME_SECKILL_STATUS.ACTIVE,
                'countdown': (sp.end_time - now).total_seconds(),
                'special_id': sp.id,  # NOTE: only return this when seckill is in active
            }

        elif now < sp.start_time:
            return {
                'title': sp.title,
                'start_time': get_timestamp_epoch(sp.start_time),
                'end_time': get_timestamp_epoch(sp.end_time),
                # "status" and "countdown" is DEPRECATED as maybe cached
                # use "start_time" and "end_time" instead
                'status': SERVICE_HOME_SECKILL_STATUS.NOT_START,
                'countdown': (sp.start_time - now).total_seconds(),
            }

        else:
            return {
                'title': sp.title,
                'start_time': None,
                'end_time': None,
                # "status" and "countdown" is DEPRECATED as maybe cached
                # use "start_time" and "end_time" instead
                'status': SERVICE_HOME_SECKILL_STATUS.NONE,
                'countdown': 0,
            }

    @classmethod
    def get_special_info_by_ids(cls, special_ids):
        """
        根据id获取专题基本信息
        :param special_ids:
        :return: [{'id':1, 'name': 'zhuanti1'}...]
        """
        try:
            if not special_ids:
                return []
            special_ids = map(int, special_ids)
            now = datetime.datetime.now()
            common_filter = Q(id__in=special_ids, is_online=True, start_time__lt=now)
            end_time_filter = Q(end_time__gt=now) | Q(end_time__isnull=True)
            info_list = list(cls.objects.filter(common_filter & end_time_filter).values('id', 'title'))
            info_list.sort(key=lambda x:special_ids.index(x['id']))
            return info_list
        except Exception as e:
            logging_exception()
            return []


#秒杀美购(多属性)活动开关
class SpecialSeckillButton(models.Model):
    class Meta:
        verbose_name = u'秒杀美购(多属性)活动开关'
        app_label = 'api'

    button_document = models.CharField(verbose_name=u'文案', max_length=100)
    start_show_time = models.DateTimeField(verbose_name=u'按钮开始出现时间')
    end_show_time = models.DateTimeField(verbose_name=u'按妞结束出现时间')
    start_sell_time = models.DateTimeField(verbose_name=u'默认开始售卖时间')
    end_sell_time = models.DateTimeField(verbose_name=u'默认结束售卖时间')
    button_prefix = models.CharField(verbose_name=u'前缀', max_length=100)
    content = models.TextField(verbose_name=u'活动详细说明')
    activity_type = models.CharField(verbose_name=u'活动类型', max_length=8, choices=DOCTOR_SECKILL_APPLY_TYPE,
                                     default=DOCTOR_SECKILL_APPLY_TYPE.NORMAL)
    discount_rate = models.IntegerField(verbose_name=u'折扣比例', blank=True, null=True, default=None)
    fixed_price = models.IntegerField(verbose_name=u'固定价格', blank=True, null=True, default=None)
    stock_limit = models.IntegerField(verbose_name=u'库存上限', blank=True, null=True, default=None)
    cities = models.ManyToManyField(City, verbose_name=u'展示城市')
    tags = models.ManyToManyField(Tag, verbose_name=u'关联tag')
    doctors = models.ManyToManyField(Doctor, verbose_name=u"关联商户")
    range_type = models.IntegerField(choices=RANGE_TYPE, default=RANGE_TYPE.WHOLE_CITY, verbose_name=u'范围类型')



#秒杀美购(多属性)申请

class DoctorSeckillApply(models.Model):
    class Meta:
        verbose_name = u'秒杀美购(多属性)申请'
        app_label = 'api'
        unique_together = (('service_item', 'seckill_button'),)

    service_item = models.ForeignKey(ServiceItem, verbose_name=u'多属性')
    seckill_price = models.IntegerField(verbose_name=u'活动价', default=999999)
    stock = models.IntegerField(verbose_name=u'库存／限购数量')
    available_num = models.IntegerField(verbose_name=u'当前可售数量', blank=True, null=True, default=None)
    seckill_button = models.ForeignKey(SpecialSeckillButton, verbose_name=u'秒杀活动')
    status = models.CharField(verbose_name=u'状态', max_length=8, choices=DOCTOR_SECKILL_APPLY_STATUS,
                              default=DOCTOR_SECKILL_APPLY_STATUS.DEFAULT)
    pre_payment_price = models.IntegerField(verbose_name=u'预付款', blank=True, null=True, default=None)
    commission = models.IntegerField(verbose_name=u'抽成', blank=True, null=True, default=None)
    self_support_discount = models.IntegerField(verbose_name=u'自营抽成', blank=True, null=True, default=None)
    pass_time = models.DateTimeField(verbose_name=u'通过时间', blank=True, null=True, default=None)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')
    latest_apply_time = models.DateTimeField(default=datetime.datetime.now, verbose_name="最近一次医生提报时间")
    # http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=28907071
    left_stock = models.IntegerField(verbose_name=u'剩余库存', default=0)

    def decrease_available_num(self, num=1, force=False):
        qs = DoctorSeckillApply.objects.filter(id=self.id)
        if not force:
            qs = qs.filter(available_num__gte=num)
        return qs.update(available_num=F(u'available_num') - num)

    def increase_available_num(self, num=1):
        return DoctorSeckillApply.objects.filter(id=self.id).update(available_num=F(u'available_num') + num)

    def increase_stock(self, num=1):
        return DoctorSeckillApply.objects.filter(id=self.id).update(stock=F(u'stock') + num,
                                                                    available_num=F(u'available_num') + num)

    def decrease_stock(self, num=1):
        return DoctorSeckillApply.objects.filter(available_num__gte=num, id=self.id, stock__gte=num).update(
            available_num=F(u'available_num') - num, stock=F(u'stock') + num)

#专题项目
class SpecialItem(models.Model):
    """
    专题绑定的是这一个,serviceitem 版本改动在双十二
    """

    class Meta:
        verbose_name = u'专题项目'
        verbose_name_plural = u'专题项目'
        app_label = 'api'

    name = models.CharField(max_length=100, default=u'', blank=True, null=True, verbose_name=u'名称',
                            help_text=u"专家姓名、帖子标题、福利标题")
    subtitle = models.CharField(max_length=100, default=u'', blank=True, null=True, verbose_name=u'一句话描述',
                                help_text=u"专家title、帖子副标题")
    image_url = models.CharField(max_length=300, default=u'', blank=True, null=True, verbose_name=u'图片地址')
    doctor = models.ForeignKey(Doctor, default=None, blank=True, null=True, verbose_name=u'专家')
    topic_id = models.IntegerField(default=None, blank=True, null=True, verbose_name=u'话题')
    diary_id = models.IntegerField(default=None, blank=True, null=True, verbose_name=u'案例')
    service = models.ForeignKey(Service, default=None, null=True, blank=True, verbose_name=u'福利')
    special = models.ForeignKey(Special, related_name='items', null=True)
    # rank 不再使用,新增position作为绝对位置设定 by zhongchengyang 20180427
    rank = models.IntegerField(default=0, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")
    position = models.IntegerField(u'列表中展示的位置', default=0, db_index=True)  # 同一城市的位置不能重复
    is_recommend = models.BooleanField(verbose_name=u'置顶', default=False)
    serviceitem = models.ForeignKey(ServiceItem, verbose_name=u'美购属性', null=True)
    doctorseckillapply = models.ForeignKey(DoctorSeckillApply, verbose_name=u'医生申请ID', null=True, default=None)
    sell_num = models.IntegerField(verbose_name=u'专场中已售数量', blank=True, null=True, default=0)

    tip = models.CharField(max_length=128, null=False, verbose_name=u'提示标签', default=u'')
    floor_id = models.IntegerField(verbose_name=u'楼层ID', null=True)

    @property
    def price(self):
        if self.doctorseckillapply is None:
            return self.serviceitem.gengmei_price
        else:
            return self.doctorseckillapply.seckill_price

    @rpc.db_opt.select_related(
        'doctor',
        'doctor__hospital__city',
        'service',
        'service__doctor__hospital__city',
    )
    def special_items(self, special_type):
        special_items = {}
        if special_type == Special.SPECIAL_TYPE.DOCTOR and self.doctor_id:
            special_items = {
                "doctor_id": self.doctor_id,
                "doctor_name": self.doctor.name,
                "image": self.image_url,
                "desc": self.subtitle,
                "province_id": self.doctor.hospital.city.province_id,
            }

        elif special_type == Special.SPECIAL_TYPE.TOPIC and self.topic_id:
            special_items = {
                "topic_id": self.topic_id,
                "title": self.name,
                "image": self.image_url,
                "subtitle": self.subtitle,
                "label": self.subtitle,
                "province_id": "",
            }

        elif special_type == Special.SPECIAL_TYPE.USERCASE and self.diary_id:
            special_items = {
                "diary_id": self.diary_id,
                "title": self.name,
                "image": self.image_url,
                "subtitle": self.subtitle,
                "label": self.subtitle,
                "province_id": "",
            }

        elif special_type == Special.SPECIAL_TYPE.SERVICE and self.service_id:
            special_items = {
                "service_id": self.service_id,
                "service_name": self.name,
                "image": self.image_url,
                # 3.9.4开始福利专题采用正方形图
                "image_header": self.service.image_header,
                "original_price": str(self.service.original_price),
                "total_price": str(self.service.gengmei_price),
                "city": self.service.doctor.hospital.city.name,
                "province_id": self.service.doctor.hospital.city.province_id,
            }

        return special_items

    def __unicode__(self):
        return self.name

    def check_constraint_city_position(self, pos):
        try:
            city = self.serviceitem.service.doctor.hospital.city
        except:
            city = None
        if not pos or not city:
            return True
        check_qs = self.special.items.filter(
            serviceitem__service__doctor__hospital__city=city,
            position=pos,
        ).exclude(id=self.id)
        if check_qs.exists():
            raise GaiaRPCFaultException(
                error=ERROR.UNIVERSAL,
                message=u'[%s](%s)已存在' % (city.name, pos),
                data=None
            )
        return True

    def set_position(self, pos):
        self.check_constraint_city_position(pos)
        self.position = pos
        self.save(update_fields=['position'])

    def get_price_info(self):
        """
        获取specialitem在专场中的价格信息
        create by oldman at 2017-01-01
        :return: specialitem专题的价格信息
        """
        try:
            if self.special.is_seckill:
                activity_type = ACTIVITY_TYPE_ENUM.SECKILL
            elif self.special.groupbuy_id:
                activity_type = ACTIVITY_TYPE_ENUM.GROUPBUY
            elif self.special.is_more_buy and self.special.more_buy_count:
                activity_type = ACTIVITY_TYPE_ENUM.MOREBUY
            else:
                activity_type = ACTIVITY_TYPE_ENUM.SPECIAL

            rule_info = SKUPriceRule.objects.get(
                activity_type=activity_type,
                activity_id=self.special.id
            )
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise ValueError(u'get specialitem priceRule error')
        try:
            return self.serviceitem.get_price_info(selling_rule_id=rule_info.id)
        except:
            info_logger.info(__import__('traceback').format_exc())
            return None

    def disable_price(self):
        """
        禁用specialitem在专场中的价格信息
        http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=28907071
        @产品: 邓光宇
        @功能: 删除美购, 剩余库存还原
        """
        try:
            if self.special.is_seckill:
                activity_type = ACTIVITY_TYPE_ENUM.SECKILL
            elif self.special.groupbuy_id:
                activity_type = ACTIVITY_TYPE_ENUM.GROUPBUY
            else:
                activity_type = ACTIVITY_TYPE_ENUM.SPECIAL
            rule_info = SKUPriceRule.objects.get(
                activity_type=activity_type,
                activity_id=self.special.id
            )
            if self.special.is_seckill:
                sip = ServiceItemPrice.objects.filter(
                    service_item_id=self.serviceitem.id,
                    selling_rule_id=rule_info.id).first()
                if sip:
                    self.doctorseckillapply.left_stock = F('left_stock') + sip.sale_limit
                    self.doctorseckillapply.save()

            ServiceItemPrice.objects \
                .filter(service_item_id=self.serviceitem.id, selling_rule_id=rule_info.id) \
                .update(is_enable=False, sale_limit=0, sale_limit_lte_zero=True)
        except:
            info_logger.info(__import__('traceback').format_exc())

    def update_or_create_price_info(self, add_stock=0):
        """
        根据specialitem获取或者创建该serviceitem在该专场的价格信息的价格信息
        :type add_stock:库存添加数量
        :return:specialitem专题的价格信息
        create by oldman 2017-01-01
        """
        try:
            if self.special.is_seckill:
                activity_type = ACTIVITY_TYPE_ENUM.SECKILL
            elif self.special.groupbuy_id:
                activity_type = ACTIVITY_TYPE_ENUM.GROUPBUY
            elif self.special.is_more_buy and self.special.more_buy_count:
                activity_type = ACTIVITY_TYPE_ENUM.MOREBUY
            else:
                activity_type = ACTIVITY_TYPE_ENUM.SPECIAL

            rule_info = SKUPriceRule.objects.get(
                activity_type=activity_type,
                activity_id=self.special.id
            )
        except:
            info_logger.info(__import__('traceback').format_exc())
            raise ValueError(u'get specialitem priceRule error')
        try:
            with transaction.atomic():
                service_item = ServiceItem.objects.select_for_update().get(id=self.serviceitem_id)

                price_infos = ServiceItemPrice.objects.filter(service_item_id=service_item.id,
                                                              selling_rule_id=rule_info.id)

                if len(price_infos) > 1:
                    raise ValueError(u'specialitem has more than 1 price')

                already_created = True if len(price_infos) == 1 else False

                if activity_type == ACTIVITY_TYPE_ENUM.MOREBUY:
                    serviceitem_price_info = service_item.get_parent().get_default_price_info()
                else:
                    serviceitem_price_info = service_item.get_default_price_info()

                if serviceitem_price_info.get('is_enable') is False:
                    raise ValueError(u'service item default price is disable')

                original_price = serviceitem_price_info.get('original_price', 0)
                cash_back_rate = serviceitem_price_info.get('cash_back_rate', 0)
                cash_back_fee = serviceitem_price_info.get('cash_back_fee', 0)

                sale_limit = 999999

                if self.doctorseckillapply:
                    discount = self.doctorseckillapply.commission
                    pre_payment_price = self.doctorseckillapply.pre_payment_price
                    gengmei_price = self.doctorseckillapply.seckill_price
                    commission_rate = self.special.commission_rate
                    pre_payment_price_rate = self.special.pre_payment_price_rate
                    # 使用活动设置的抽佣比例和预付款比例
                    if commission_rate and pre_payment_price_rate:
                        discount = math.ceil(gengmei_price * (commission_rate * 1.0 / 100))
                        pre_payment_price = math.ceil(gengmei_price * (pre_payment_price_rate * 1.0 / 100))
                    # 如果剩余库存充足, 允许添加到其他专场

                    self_support_discount = 0
                    if self.special.is_seckill:
                        assert add_stock > 0 \
                               and self.doctorseckillapply.stock \
                               and self.doctorseckillapply.left_stock \
                               and self.doctorseckillapply.left_stock - add_stock >= 0
                        sale_limit = add_stock
                        self.doctorseckillapply.left_stock = F(u'left_stock') - add_stock
                        self.doctorseckillapply.save()
                        self_support_discount = self.doctorseckillapply.self_support_discount
                        if not self_support_discount:
                            self_support_discount = 0
                    else:
                        sale_limit = self.doctorseckillapply.stock
                else:
                    discount = serviceitem_price_info.get('discount', 0)
                    pre_payment_price = serviceitem_price_info.get('pre_payment_price', 0)
                    gengmei_price = serviceitem_price_info.get('gengmei_price', 0)
                    self_support_discount = serviceitem_price_info.get('self_support_discount', 0)

                if not self.doctorseckillapply and (self.special.is_seckill or self.special.groupbuy_id):
                    # 如果是秒杀专场并且不是医生提报的，需要给售卖数量，目前基础值为20
                    sale_limit = 20 + add_stock
                    if sale_limit < 0:
                        raise ValueError(u'stock is less than 0')

                # 秒杀一个用户只能购买一个
                single_user_buy_limit = 1 if self.special.is_seckill or self.special.groupbuy_id else 0

                # 多买判断 价格变更
                more_buy_price = 0

                if self.doctorseckillapply and self.special.is_more_buy \
                and self.special.more_buy_count > 1 and self.serviceitem.parent_id:
                    more_buy_price = gengmei_price
                    gengmei_price = self.doctorseckillapply.seckill_price * self.special.more_buy_count
                    original_price = original_price * self.special.more_buy_count
                    pre_payment_price = pre_payment_price * self.special.more_buy_count
                    more_buy_price = self.doctorseckillapply.seckill_price

                if already_created:
                    price_id = price_infos[0].id

                    service_item_price = service_item.update_price(
                        price_id=price_id,
                        is_enable=True,
                        discount=discount,
                        pre_payment_price=pre_payment_price,
                        original_price=original_price,
                        gengmei_price=gengmei_price,
                        cash_back_rate=cash_back_rate,
                        cash_back_fee=cash_back_fee,
                        self_support_discount=self_support_discount,
                        single_user_buy_limit=single_user_buy_limit,
                        more_buy_price=more_buy_price,
                    )

                    sale_limit_lte_zero = sale_limit <= 0
                    fake_persent = random.randint(110, 150) * 0.01
                    total_num = sale_limit
                    total_num_fake = round(total_num * fake_persent)
                    ServiceItemPrice.objects.filter(id=price_id).update(sale_limit=sale_limit,
                                                                        total_num=total_num,
                                                                        total_num_fake=total_num_fake,
                                                                        sale_limit_lte_zero=sale_limit_lte_zero)
                else:
                    service_item_price = service_item.create_price(
                        sale_limit=sale_limit,
                        is_default_price=False,
                        selling_rule_id=rule_info.id,
                        discount=discount,
                        pre_payment_price=pre_payment_price,
                        original_price=original_price,
                        gengmei_price=gengmei_price,
                        cash_back_rate=cash_back_rate,
                        cash_back_fee=cash_back_fee,
                        self_support_discount=self_support_discount,
                        single_user_buy_limit=single_user_buy_limit,
                        more_buy_price=more_buy_price,
                    )
                al = all([
                    service_item_price,
                    self.doctorseckillapply_id,
                    self.special.is_seckill is False,
                    self.special.is_more_buy is False,
                ])
                if al:
                    sstock, created = SpecialStock.objects.get_or_create(
                        doctorseckillapply_id=self.doctorseckillapply_id,
                        defaults={'stock': self.doctorseckillapply.stock})
                    if sstock.price_id:
                        serviceitem_price_ids = json.loads(sstock.price_id)
                        serviceitem_price_ids.append(service_item_price.id)
                    else:
                        serviceitem_price_ids = [service_item_price.id]
                    sstock.price_id = json.dumps(list(set(serviceitem_price_ids)))
                    sstock.save()

        except AssertionError:
            raise AssertionError('剩余可配置的库存数量' + str(self.doctorseckillapply.left_stock))
        except Exception:
            info_logger.info(__import__('traceback').format_exc())
            raise ValueError('save price info error')

#秒杀美购多属性审核记录
class DoctorSeckillApplyRecord(models.Model):
    class Meta:
        verbose_name = u'秒杀美购(多属性)审核记录'
        app_label = 'api'

    doctorseckillapply = models.ForeignKey(DoctorSeckillApply, verbose_name=u'关联申请', related_name=u'records')
    is_pass = models.BooleanField(verbose_name=u'是否通过', default=False)
    seckill_price = models.IntegerField(verbose_name=u'活动价', default=999999)
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')
    person = models.ForeignKey(Person, verbose_name=u'审核人')
    comment = models.CharField(max_length=500, verbose_name=u'备注')

#秒杀美购
class SpecialSeckillService(models.Model):
    class Meta:
        verbose_name = u'秒杀美购'
        app_label = 'api'
        db_table = 'api_specialseckillservice'
        unique_together = (('service_item', 'special', 'service'),)

    service = models.ForeignKey(Service, verbose_name=u'美购')
    service_item = models.ForeignKey(ServiceItem, verbose_name=u'多属性', null=True)
    special = models.ForeignKey(Special, related_name='seckill_services')
    seckill_price = models.IntegerField(verbose_name=u'秒杀价', default=999999)
    pre_payment_price = models.IntegerField(verbose_name=u'预付款', default=999999)
    stock = models.IntegerField(default=0, null=True, blank=True, verbose_name=u'库存')
    commission = models.IntegerField(verbose_name=u'抽成', default=999999)
    buy_up_to = models.IntegerField(verbose_name=u'每人限购多少件', default=1)
    start_time = models.DateTimeField(verbose_name=u'起始时间', default=timezone.now)
    end_time = models.DateTimeField(verbose_name=u'结束时间', null=True, blank=True)
    tip = models.CharField(max_length=128, null=False, verbose_name=u'提示标签')
    seckill_apply = models.ForeignKey(DoctorSeckillApply, verbose_name=u'关联秒杀美购申请', blank=True,
                                      null=True, on_delete=models.SET_NULL)
    rank = models.IntegerField(default=0, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")

    @classmethod
    def fetch(cls, service, itemkey=None):
        if itemkey is None:
            service_item = ServiceItem.objects.filter(parent_id=0, service_id=service.id, is_delete=False)
        else:
            service_item = ServiceItem.objects.filter(parent_id=0, service_id=service.id, is_delete=False, key=itemkey)
        now = datetime.datetime.now()
        try:
            item = cls.objects.filter(
                service=service,
                service_item__in=service_item,
                start_time__lte=now,
                end_time__gte=now,
                special__is_online=True,
                stock__gt=0,
            ).order_by('seckill_price').first()
            return item
        except cls.DoesNotExist:
            return None

    @classmethod
    def fetch_item_seckill(cls, service, item):
        now = datetime.datetime.now()
        try:
            item = cls.objects.filter(
                service=service,
                service_item=item,
                start_time__lte=now,
                end_time__gte=now,
                special__is_online=True,
                stock__gt=0,
            ).order_by('seckill_price').first()
            return item
        except cls.DoesNotExist:
            return None

    def is_can_be_sold(self):
        now = datetime.datetime.now()
        time_valid = self.start_time <= now and now <= self.end_time
        if time_valid and self.stock >= 1:
            return True

        return False


#秒杀美购提醒
class SeckillNotify(models.Model):
    class Meta:
        verbose_name = u'秒杀美购提醒'
        app_label = 'api'
        db_table = 'api_seckillnotify'

    service = models.ForeignKey(Service, verbose_name=u'美购')
    special = models.ForeignKey(Special, verbose_name=u'专题')
    service_item = models.ForeignKey(ServiceItem, verbose_name=u'专题')
    person = models.ForeignKey(Person)
    notification_added = models.BooleanField(verbose_name=u'是否已添加通知', default=False)
    push_sent = models.BooleanField(verbose_name=u'是否发送过推送', default=False)
    sms_sent = models.BooleanField(verbose_name=u'是否发送过短信', default=False)

#专题豆腐块
class SpecialGadget(models.Model):
    class Meta:
        verbose_name = u'专题豆腐块'
        app_label = 'api'

    special = models.ForeignKey(Special, related_name='Special_Gadget')
    ordering = models.IntegerField(default=20, verbose_name=u"展示顺序", help_text=u"小的排在前，大的排在后")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u'添加时间')
    image_data = models.TextField(verbose_name='模板数据', default='[]')

    @classmethod
    def get_gadgets(cls, special_id):
        res = {
            'gadgets': [],
        }
        try:
            special = Special.objects.get(id=special_id)
        except Special.DoesNotExist:
            return res

        gadgets = cls.objects.filter(
            special=special
        ).order_by('ordering', 'created_time')

        for gadget in gadgets:
            image_data = json.loads(gadget.image_data)
            # 单独处理圈子详情页跳转
            for data_param in image_data:
                if data_param['url_type'] == SLIDE_TYPE.TAG:
                    tag_id = int(data_param['url_params']['id'])
                    data_param['url_params']['name'] = Tag.objects.get(id=tag_id).name
                if data_param['url_type'] == SLIDE_TYPE.WIKI:
                    from pprint import pprint
                    pprint(image_data)
                    wiki_id = int(data_param['url_params']['id'])
                    data_param['url_params']['name'] = ItemWiki.objects.get(id=wiki_id).item_name
                if data_param['url_type'] == '29':
                    data_param['url_type'] = SLIDE_TYPE.SERVICE
                    data_param['url_params']['service_item_id'] = data_param['url_params']['id']
                    seritem = ServiceItem.objects.get(id=data_param['url_params']['id'])
                    data_param['url_params']['id'] = seritem.service_id
                elif data_param['url_type'] == SLIDE_TYPE.SPECIAL:
                    try:
                        s = Special.objects.get(id=data_param['url_params']['id'], is_online=True)
                    except Special.DoesNotExist:
                        s = None
                    data_param['url_params']['is_new_special'] = s.is_new_special if s else False
            res['gadgets'].append(image_data)

        return res

#秒杀专题资源位
class SpecialResource(models.Model):
    class Meta:
        verbose_name = u'秒杀专题资源位'
        app_label = 'api'

    special = models.ForeignKey(Special, verbose_name=u'关联专题')
    rank = models.IntegerField(u'排序', default=200)
    image_data = models.TextField(u'关联内容')

    def data(self):
        return {
            'content': json.loads(self.image_data) if self.image_data else ''
        }

#专题布局
class SpecialLayout(models.Model):
    class Meta:
        verbose_name = u'专题布局'
        unique_together = ('special', 'module')
        app_label = 'api'

    special = models.ForeignKey(Special, verbose_name=u'关联专题')
    is_visible = models.BooleanField(u'是否显示', default=False)
    module = models.IntegerField(u'专题模块类型', choices=SPECIAL_MODULE)
    related = models.TextField(u'关联内容')

    @classmethod
    def special_layouts_data(cls, special_id):
        result = {}
        layouts = cls.objects.filter(special_id=special_id)
        if layouts.count() != 0:
            result = {
                obj.module: obj.data()
                for obj in layouts
            }
        service_data = result.get(SPECIAL_MODULE.FLOOR, None)
        outer_content = service_data.get('content') if service_data else {}
        floor = outer_content.get('floor', []) if outer_content else []
        for inner_data in floor:
            inner_content = inner_data.get('content', [])
            for data_inner in inner_content:
                message_list = data_inner.get('message', [])
                for real_data in message_list:
                    if real_data['jump_type'] == '29':
                        real_data['jump_type'] = str(SLIDE_TYPE.SERVICE)
                        real_data['service_item_id'] = real_data['jump_url']
                        seritem = ServiceItem.objects.get(id=real_data['service_item_id'])
                        real_data['jump_url'] = str(seritem.service_id)
                    if str(real_data['jump_type']) == str(SLIDE_TYPE.SPECIAL):
                        real_data['is_new_special'] = Special.objects.get(id=real_data['jump_url']).is_new_special

                    elif real_data['jump_type'] == SLIDE_TYPE.SPECIAL:
                        try:
                            s = Special.objects.get(id=real_data['url_params'], is_online=True)
                        except Special.DoesNotExist:
                            s = None
                        real_data['is_new_special'] = s.is_new_special if s else False
        return result

    def data(self):
        return {
            'is_visible': self.is_visible,
            'module_type': self.module,
            'content': json.loads(self.related) if self.related else ''
        }

    @classmethod
    def layout_data(cls, special_id, module=SPECIAL_MODULE.FLOOR):
        """
        20170417 module字段增加其他类型 只通过special_id进行get操作会出现返回多个的情况 因此增加默认参数
        :param special_id: 专题id
        :param module: 模块类型
        :return:
        """
        try:
            obj = cls.objects.get(special_id=special_id, module=module)
        except:
            return None
        all_data_info = {
            'is_visible': obj.is_visible,
            'module_type': obj.module,
            'content': json.loads(obj.related) if obj.related else ''
        }
        service_data = all_data_info.get('content')
        if isinstance(service_data, dict):
            floor_data = service_data.get('floor', [])
            for inner_content_data in floor_data:
                for inner_data in inner_content_data['content']:
                    for real_data in inner_data['message']:
                        if real_data['jump_type'] == '29':
                            real_data['jump_type'] = str(SLIDE_TYPE.SERVICE)
                            real_data['service_item_id'] = real_data['jump_url']
                            seritem = ServiceItem.objects.get(id=real_data['service_item_id'])
                            real_data['jump_url'] = str(seritem.service_id)
                        if str(real_data['jump_type']) == str(SLIDE_TYPE.SPECIAL):
                            real_data['is_new_special'] = Special.objects.get(id=real_data['jump_url']).is_new_special
        return all_data_info

    @classmethod
    def inject_params(cls, data):
        """
        替换参数
        :param data:
        :return:
        """

        def _inner_format(data):
            if data.get('jump_type') == SLIDE_TYPE.TAG:
                tag_id = int(data['url_params'])
                data['name'] = Tag.objects.get(id=tag_id).name
            if data.get('jump_type') == SLIDE_TYPE.WIKI:
                wiki_id = int(data['url_params'])
                data['name'] = ItemWiki.objects.get(id=wiki_id).item_name
            if data.get('jump_type') == '29':
                data['service_item_id'] = data.pop('url_params', '')
                s = ServiceItem.objects.get(id=data['service_item_id'])
                data['jump_type'] = str(SLIDE_TYPE.SERVICE)
                data['url_params'] = str(s.service_id)
            if data.get('jump_type') == SLIDE_TYPE.SPECIAL:
                try:
                    s = Special.objects.get(id=data['url_params'], is_online=True)
                except Special.DoesNotExist:
                    s = None
                data['is_new_special'] = s.is_new_special if s else False

        if not data:
            return {}

        if isinstance(data, list):
            for ss in data:
                _inner_format(ss)
        else:
            _inner_format(data)

    @classmethod
    def new_special_layouts_data(cls, special_id, module=SPECIAL_MODULE.FLOOR):
        try:
            obj = cls.objects.get(special_id=special_id, module=module)
        except:
            return {}

        all_data_info = {
            'is_visible': obj.is_visible,
            'module_type': obj.module,
            'data': json.loads(obj.related) if obj.related else ''
        }

        related = json.loads(obj.related) if obj.related else []

        if all([obj.special.is_new_special, obj.module == SPECIAL_MODULE.VIDEO]):
            # 视频 针对SKU特殊处理
            for kk in related:
                cls.inject_params(kk)

        if all([obj.special.is_new_special, obj.module == SPECIAL_MODULE.GADGET_TEMPLATE]):
            # 豆腐块 针对SKU特殊处理
            for kk in related:
                for dd in kk.get('data', []):
                    cls.inject_params(dd)

        all_data_info['data'] = related

        return all_data_info

    @classmethod
    def get_gadgets(cls, special_id):
        try:
            obj = cls.objects.get(special_id=special_id, module=SPECIAL_MODULE.GADGET_TEMPLATE)
        except:
            return {}
        all_data_info = {
            'is_visible': obj.is_visible,
            'module_type': obj.module,
            'data': json.loads(obj.related) if obj.related else []
        }

        content = all_data_info['data']

        for dd in content:
            for ss in dd.get('data', []):
                cls.inject_params(ss)

        all_data_info['data'] = content

        return all_data_info


#专题美购外显

class SpecialExterior(RequestSourceModel):
    class Meta:
        verbose_name = u'专题美购外显'
        app_label = 'api'

    name = models.CharField(max_length=64, verbose_name=u'名称')
    image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, max_length=300, verbose_name=u'专题美购外显图片地址', default=u'', )
    start_time = models.DateTimeField(verbose_name=u'生效时间', blank=True, null=True)
    end_time = models.DateTimeField(verbose_name=u'下线时间', blank=True, null=True)
    rank = models.IntegerField(default=100, verbose_name=u"展示顺序")
    is_online = models.BooleanField(default=False, verbose_name=u'是否上线')
    regions = models.ManyToManyField(Region, verbose_name=u'展示大区', through=u'SpecialExteriorRegion')
    cities = models.ManyToManyField(City, verbose_name=u'展示城市', through=u'SpecialExteriorCity')
    special = models.ForeignKey(Special, verbose_name=u'关联专题', null=True)
    services = models.ManyToManyField(Service, verbose_name=u'外显美购', through=u'SpecialExteriorService')
    show_position = models.IntegerField(verbose_name=u'美购外显展示位置', choices=SPECIAL_EXTRA_POSITION,
                                        default=SPECIAL_EXTRA_POSITION.SERVICE_HOMIE)
    user_type = models.CharField(max_length=2, choices=SLIDE_USER_TYPE, default=SLIDE_USER_TYPE.ALL_USER)
    payment_type = models.CharField(max_length=2, choices=SLIDE_PAYMENT_TYPE, default=SLIDE_PAYMENT_TYPE.ALL_PAYMENT)
    new_image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, max_length=255, verbose_name=u'新版首页专题美购外显图片', default=u'', )
    new_big_image = ImgUrlField(img_type=IMG_TYPE.SPECIAL, max_length=255, verbose_name=u'新版首页专题美购外显大图', default=u'', )
    special_polymer_id = models.IntegerField(verbose_name=u'秒杀聚合页id', null=True, db_index=True)
    aggregate_type = models.IntegerField(verbose_name=u'聚合类型', max_length=3, choices=POLYMER_TYPE, default=POLYMER_TYPE.SECKILL_POLYMER)

    @classmethod
    def get_special_exterior(cls, special_id, show_position):
        exteriors = cls.objects.filter(special_id=special_id, show_position=show_position, is_online=True)
        if not exteriors:
            return []

        result = []
        for exterior in exteriors:
            data = {
                'special_id': exterior.special_id,
                'banner': exterior.image,
            }
            services = exterior.services.all()
            data['services'] = [
                {
                    'id': s.id,
                    'tag_name': (s.tags.first() or '') and s.tags.first().name,
                    'doctor_name': s.doctor and s.doctor.name,
                    'doctor_is_officer': s.doctor and s.doctor.doctor_type == DOCTOR_TYPE.OFFICER or False,
                    'hospital_name': s.hospital and s.hospital.name,
                    'gengmei_prices': s.gengmei_price_list,
                    'image_header': s.image_header,
                    'city_name': s.city_name,
                    'original_price': s.lowest_original_price,
                }
                for s in services
            ]
            result.append(data)

        return result


class SpecialExteriorRegion(models.Model):
    class Meta:
        verbose_name = u'专题外显展示大区'
        app_label = u'api'

    special_exterior = models.ForeignKey(SpecialExterior, verbose_name=u'关联美购外显')
    region = models.ForeignKey(Region, verbose_name=u'关联大区')


class SpecialExteriorCity(models.Model):
    class Meta:
        verbose_name = u'专题外显展示城市'
        app_label = u'api'

    special_exterior = models.ForeignKey(SpecialExterior, verbose_name=u'关联美购外显')
    city = models.ForeignKey(City, verbose_name=u'关联城市')


class SpecialExteriorService(models.Model):
    class Meta:
        verbose_name = u'专题外显展示美购'
        app_label = u'api'

    special_exterior = models.ForeignKey(SpecialExterior, verbose_name=u'关联美购外显')
    service = models.ForeignKey(Service, verbose_name=u'关联美购')


class SpecialRule(models.Model):
    class Meta:
        db_table = 'api_special_rule'

    special = models.ForeignKey(Special)
    tag = models.ForeignKey(Tag)


class SpecialService(models.Model):
    # 专场-美购 通过某种py交易关联的表, 如tag
    class Meta:
        verbose_name = '美购关联表'
        db_table = 'api_specialservice_dbmw'

    special = models.ForeignKey(Special)
    service = models.ForeignKey(Service)

    @classmethod
    def update_service(cls, special_id):
        from api.models.service import ServiceTag
        special = Special.objects.get(id=special_id)
        tags = special.tags.filter(is_online=True)
        all_tags = sum([x._child_tag_closure(0) for x in tags], [])
        services = [x.service_id for x in ServiceTag.objects.filter(tag__in=all_tags, tag__is_online=True)]
        new_services = set(services)
        old_services = set(cls.objects.filter(special_id=special_id).values_list('service_id', flat=True))
        to_delete = old_services - new_services
        to_add = new_services - old_services
        cls.objects.filter(special=special, service_id__in=to_delete).delete()
        to_inserts = []
        for service_id in to_add:
            to_inserts.append(cls(special_id=special_id, service_id=service_id))
        cls.objects.bulk_create(to_inserts, batch_size=2000)

    @classmethod
    def update_with_tag(cls, tags):
        tags = Tag.objects.filter(id__in=tags)
        all_tags = sum([x._parent_tags_closure(0) for x in tags], [])
        rules = SpecialRule.objects.filter(tag__in=all_tags)
        for special_id in set([x.special_id for x in rules]):
            cls.update_service(special_id)

#拼团专场
class GroupBuySpecial(models.Model):
    class Meta:
        verbose_name = '拼团专场'
        db_table = 'api_groupbuyspecial'

    groupbuy_type = models.IntegerField(verbose_name=u'拼团类型', choices=GROUPBUY_TYPE, default=GROUPBUY_TYPE.OLD_TO_OLD)
    active_type = models.IntegerField(verbose_name=u'活动类型', choices=ACTIVE_TYPE, default=ACTIVE_TYPE.COMMON)
    nums = models.IntegerField(verbose_name=u'成团人数')
    countdown = models.IntegerField(verbose_name=u'拼团限时(秒)')
    share_wechat_images = models.TextField(verbose_name=u'分享到小程序的图片')

#专题楼层
class SpecialStorey(models.Model):
    class Meta:
        verbose_name = '专题楼层'
        db_table = 'api_specialstorey'

    special_id = models.IntegerField(verbose_name=u'专题ID', db_index=True)
    parent_id = models.IntegerField(verbose_name=u'父楼层ID', db_index=True)
    related = models.TextField(verbose_name=u'楼层数据')
    sort = models.IntegerField(verbose_name='排序')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')

    @classmethod
    def get_floor_data(cls, special_id, floor_id):
        """获取某个子楼层数据，目前仅仅获取某个子楼层数据"""

        try:
            item = cls.objects.filter(special_id=special_id, id=floor_id).order_by("-id").first()
        except:
            return {}

        return item.detail()

    @classmethod
    def get_first_floor(cls, special_id):
        """获取楼层数据"""

        floor = cls.objects.filter(special_id=special_id, parent_id__isnull=True).order_by("sort")

        result = []
        for item in floor:
            result.append(item.detail())

        return result

    @classmethod
    def get_second_floor(cls, special_id):
        """获取楼层数据"""
        floor = cls.objects.filter(special_id=special_id, parent_id__isnull=False).order_by('sort')

        result = []
        for item in floor:
            result.append(item.detail())

        return result

    def inject_params(self, data):
        """
        替换参数
        :param data:
        :return:
        """

        def _inner_format(data):
            if data.get('jump_type') == SLIDE_TYPE.TAG:
                tag_id = int(data['url_params'])
                data['name'] = Tag.objects.get(id=tag_id).name
            if data.get('jump_type') == SLIDE_TYPE.WIKI:
                wiki_id = int(data['url_params'])
                data['name'] = ItemWiki.objects.get(id=wiki_id).item_name
            if data.get('jump_type') == '29':
                data['service_item_id'] = data['url_params']
                s = ServiceItem.objects.get(id=data['service_item_id'])
                data['jump_type'] = str(SLIDE_TYPE.SERVICE)
                data['url_params'] = str(s.service_id)
            if data.get('jump_type') == SLIDE_TYPE.SPECIAL:
                try:
                    s = Special.objects.get(id=data['url_params'], is_online=True)
                except Special.DoesNotExist:
                    s = None

                data['is_new_special'] = s.is_new_special if s else False

        if not data:
            return {}

        if isinstance(data.get('data'), list):
            for ss in data.get('data', []):
                _inner_format(ss)
        else:
            _inner_format(data)

    def detail(self):
        # 不含子楼层
        child_related = json.loads(self.related) if self.related else {}

        for _data in child_related.get('data', []):
            type_id = _data.get('type_id', -1)

            if str(type_id) in [str(SPECIAL_MODULE.VIDEO), str(SPECIAL_MODULE.GADGET_TEMPLATE)]:
                self.inject_params(_data)

        return {
            "id": self.id,
            "ordering": self.sort,
            "parent_id": self.parent_id or self.id,
            "special_id": self.special_id,
            "related": json.dumps(child_related),
        }


class SpecialRelatedTag(models.Model):
    class Meta:
        verbose_name = u'新专题专题关联tag'
        verbose_name_plural = verbose_name
        app_label = 'api'
        unique_together = ('special', 'tag', 'floor_id')

    special = models.ForeignKey(Special, verbose_name=u'专题id', related_name='special_related_tags')
    tag = models.ForeignKey(Tag, verbose_name=u'专题关联tag')
    floor_id = models.IntegerField(verbose_name=u'楼层ID')


class SeckillPolymer(models.Model):
    class Meta:
        verbose_name = u'秒杀聚合页'
        verbose_name_plural = verbose_name
        app_label = 'api'

    title = models.CharField(verbose_name=u'标题', max_length=255)
    rules = models.TextField(verbose_name=u'活动规则')
    image = ImgUrlField(img_type=IMG_TYPE.POLYMER, max_length=255, verbose_name=u'专题美购外显图片地址', default=u'')
    is_online = models.BooleanField(default=True, help_text=u"是否可以上线", verbose_name=u"上线", db_index=True)
    tool_bar_json = models.TextField(verbose_name=u'tool_bar配置, json格式, 废弃')
    create_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True)
    share = models.ForeignKey(Share, default=None, verbose_name=u"分享文案", null=True)
    polymer_type = models.CharField(u'聚合类型', max_length=3, choices=POLYMER_TYPE, default=POLYMER_TYPE.SECKILL_POLYMER)
    button_selection = models.CharField(u'按钮区默认选中方式', max_length=3, choices=BUTTON_SELECTION,
                                 default=BUTTON_SELECTION.GEOGRAPHICAL)

    def get_future_special_id(self):
        """
        获取最佳的秒杀专场
        :return:
        """
        if not self.id:
            return None

        now = timezone.now()
        special_ids = list(self.seckill_polymers.values_list('special_id', flat=True))
        # 先尝试获取正在进行中
        special = Special.objects.filter(
            is_online=True, is_seckill=True,
            id__in=special_ids, seckill_type=SECKILL_TYPE.GENGMEI
        )

        started_special = special.filter(start_time__lte=now, end_time__gte=now)
        if started_special:
            return started_special.first().id

        # 全部结束
        max_end_time = special.aggregate(max_end_time=Max('end_time')).get('max_end_time')
        end_special = special.filter(end_time=max_end_time)
        if max_end_time < now:
            return end_special.first().id

        # 未开始, 取第一个
        min_start_time = special.aggregate(max_start_time=Min('start_time')).get('max_start_time')
        not_start_special = special.filter(start_time=min_start_time)
        if min_start_time > now:
            return not_start_special.first().id

    def get_related_seckill_activities(self):
        """
        获取秒杀聚合关联的秒杀专题
        :return:
        """
        ret_data = []
        special_ids = self.seckill_polymers.values_list('special_id', flat=True).order_by("special__start_time")
        for special_id in special_ids:
            sp = Special.objects.get(id=special_id)
            sp_data = Special.format_seckill_info(sp)
            if "special_id" not in sp_data:
                sp_data['special_id'] = special_id
            sp_data.update({
                "seckill_image": sp.image,
                "polymer_backgrond_image": sp.polymer_backgrond_image,
                "polymer_unselected_image": sp.polymer_unselected_image,
                "is_show_rule": bool(sp.activity_rules),
                "activity_rules": sp.activity_rules,
            })
            ret_data.append(sp_data)
        return ret_data

    def get_share_data(self):
        if self.share:
            return {
                'share_title': self.share.title,
                'share_content': self.share.content,
                'share_moments': self.share.moments,
                'share_weibo': self.share.weibo,
                'share_image': self.share.image
            }
        else:
            return {}

    def get_target_special_id_by_condition(self, city_id):
        """
        根据条件获取外显专题id
        :param city_id:
        :return:
        """

        button_objs = SeckillPolymerButton.objects.filter(seckill_polymer_id=self.id)
        if self.button_selection == BUTTON_SELECTION.GEOGRAPHICAL:
            region_id = None
            try:
                city = City.objects.get(id=city_id)
                region_id = city.province.region.id
            except City.DoesNotExist:
                logging_exception()

            if not region_id:
                region_id = -1

            for button in button_objs:
                if SpecialRegion.objects.filter(
                        special_id=button.special_id, region_id=region_id
                ).exists():
                    return button.special_id

        elif self.button_selection == BUTTON_SELECTION.FIRST:  # 按第一位进行选中
            for button in button_objs:
                return button.special_id

        return None


class SeckillRelatedSpecial(models.Model):
    class Meta:
        verbose_name = u'秒杀聚合页关联专场'
        verbose_name_plural = verbose_name
        app_label = 'api'

    special = models.ForeignKey(Special, verbose_name=u'关联专场')
    seckillpolymer = models.ForeignKey(SeckillPolymer, verbose_name=u'关联秒杀聚合页', related_name='seckill_polymers')


class SeckillPolymerLayout(models.Model):
    class Meta:
        verbose_name = u'秒杀聚合布局'
        unique_together = ('seckill_polymer', 'module')
        app_label = 'api'

    seckill_polymer = models.ForeignKey(SeckillPolymer, verbose_name=u'关联聚合')
    is_visible = models.BooleanField(u'是否显示', default=False)
    module = models.IntegerField(u'专题模块类型', choices=SECKILL_POLYMER_MODULES)
    related = models.TextField(u'关联内容')


class SpecialStock(models.Model):
    class Meta:
        verbose_name = u'专题库存映射'
        app_label = 'api'

    doctorseckillapply_id = models.IntegerField('医生提报id', unique=True)
    price_id = models.TextField('价格信息')
    stock = models.IntegerField('库存', default=0)


class SeckillPolymerButton(models.Model):
    class Meta:
        verbose_name = u'按钮区设置'
        app_label = 'api'

    seckill_polymer = models.ForeignKey(SeckillPolymer, verbose_name=u'关联聚合')
    selected_image = ImgUrlField(img_type=IMG_TYPE.NOWATERMARK, max_length=255, verbose_name=u'专场按钮选中图', default=u'')
    unselected_image = ImgUrlField(img_type=IMG_TYPE.NOWATERMARK, max_length=255, verbose_name=u'专场按钮未选中图', default=u'')
    special = models.ForeignKey(Special, verbose_name=u'关联专场')
    module = models.IntegerField(u'专题模块类型', choices=SECKILL_POLYMER_MODULES, default=SECKILL_POLYMER_MODULES.BUTTON)

    @classmethod
    def get_target_polymer_data(cls, polymer_id):
        """
        获取聚合专题的数据
        :param polymer_id:
        :return:
        """
        return [{
            "unselected_img": data_obj.unselected_image or data_obj.selected_image,
            "selected_img": data_obj.selected_image or data_obj.unselected_image,
            "special_id": data_obj.special_id,
        } for data_obj in cls.objects.filter(seckill_polymer_id=polymer_id)]


class SpecialRegion(models.Model):
    class Meta:
        verbose_name = u'新专题关联大区'
        app_label = 'api'

    region = models.ForeignKey(Region, verbose_name=u'展示大区')
    create_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True)
    special = models.ForeignKey(Special, verbose_name=u'关联专题')

    @classmethod
    def filter_special_by_region_id(cls, region_id, special_id_list):
        """
        根据大区过滤对应的专题id
        :param region_id:
        :param special_id_list:
        :return:
        """
        try:
            result_special_ids = []
            region_id = int(region_id)
            special_id_list = map(int, special_id_list)
            special_region_info = list(SpecialRegion.objects.
                                       filter(special_id__in=special_id_list).
                                       values('special_id', 'region_id')
                                    )
            sr_map_info = {}
            for sp_id in special_id_list:
                sr_map_info[sp_id] = []
            for sr in special_region_info:
                sr_map_info[sr['special_id']].append(sr['region_id'])
            for sp_id in special_id_list:
                sp_regions = sr_map_info.get(sp_id) or []
                if not sp_regions:    # 未配置, 表示全国属性
                    result_special_ids.append(sp_id)
                else:
                    if region_id in sp_regions:
                        result_special_ids.append(sp_id)

            return result_special_ids
        except Exception as e:
            logging_exception()
            return []
