package com.liquidnet.service.slime.service.impl;

import com.github.pagehelper.PageInfo;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.util.CollectionUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.param.KylinGroupBuyActivityParam;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyActivityVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyDiscountVo;
import com.liquidnet.service.kylin.dto.vo.groupbuy.KylinGroupBuyOrderVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketTimesVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.service.partner.IKylinGroupBuyPartnerService;
import com.liquidnet.service.slime.util.ObjectUtil;
import com.liquidnet.service.slime.util.QueueUtil;
import com.liquidnet.service.slime.util.RedisSlimeUtils;
import com.mongodb.BasicDBObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class KylinGroupBuyPartnerServiceImpl implements IKylinGroupBuyPartnerService {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private RedisSlimeUtils redisSlimeUtils;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private QueueUtil queueUtil;

    @Override
    public ResponseDto<PageInfo<KylinGroupBuyActivityVo>> list(String name, Integer status, Integer page, Integer size) {
        Criteria criteria = new Criteria();
        if (status != null) {
            criteria.and("status").is(status);
        }
        if (name != null && !name.trim().isEmpty()) {
            criteria.and("name").regex(".*" + name.trim() + ".*", "i");
        }
        Query countQuery = Query.query(criteria);
        long total = mongoTemplate.count(countQuery, KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        Query dataQuery = Query.query(criteria).with(Sort.by(Sort.Direction.DESC, "createdAt"));
        if (page != null && size != null && page > 0 && size > 0) {
            dataQuery.skip((long) (page - 1) * size).limit(size);
        }
        List<KylinGroupBuyActivityVo> list = mongoTemplate.find(dataQuery,
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        if (!list.isEmpty()) {
            List<String> activityIds = list.stream().map(KylinGroupBuyActivityVo::getActivityId).collect(Collectors.toList());
            Map<String, Long> orderCountMap = new HashMap<>();
            for (String activityId : activityIds) {
                long count = mongoTemplate.count(Query.query(Criteria.where("activityId").is(activityId)),
                        KylinGroupBuyOrderVo.class.getSimpleName());
                orderCountMap.put(activityId, count);
            }
            for (KylinGroupBuyActivityVo vo : list) {
                vo.setOrderCount(orderCountMap.getOrDefault(vo.getActivityId(), 0L));
                KylinPerformanceVo performanceVo = redisSlimeUtils.getPerformanceVo(vo.getPerformanceId());
                vo.setPerformanceName(performanceVo != null ? performanceVo.getTitle() : null);
            }
        }
        PageInfo<KylinGroupBuyActivityVo> pageInfo = new PageInfo<>(list);
        pageInfo.setTotal(total);
        return ResponseDto.success(pageInfo);
    }

    @Override
    public ResponseDto<Map<String, Long>> stats() {
        Map<String, Long> result = new HashMap<>();
        String collection = KylinGroupBuyActivityVo.class.getSimpleName();
        result.put("totalActivities", mongoTemplate.count(Query.query(new Criteria()), collection));
        result.put("activeCount", mongoTemplate.count(
                Query.query(Criteria.where("status").is(GroupBuyStatusConst.ActivityStatus.ACTIVE)), collection));
        result.put("endedCount", mongoTemplate.count(
                Query.query(Criteria.where("status").is(GroupBuyStatusConst.ActivityStatus.ENDED)), collection));
        result.put("orderCount", mongoTemplate.count(Query.query(new Criteria()),
                KylinGroupBuyOrderVo.class.getSimpleName()));
        return ResponseDto.success(result);
    }

    @Override
    public ResponseDto<KylinGroupBuyActivityVo> detail(String activityId) {
        KylinGroupBuyActivityVo vo = findActivity(activityId);
        return vo == null ? ResponseDto.failure("拼团活动不存在") : ResponseDto.success(vo);
    }

    @Override
    public ResponseDto<KylinGroupBuyActivityVo> create(KylinGroupBuyActivityParam param) {
        ResponseDto<Boolean> validate = validateParam(param);
        if (!validate.isSuccess()) {
            return ResponseDto.failure(validate.getMessage());
        }
        String now = now();
        KylinGroupBuyActivityVo vo = buildActivity(param, now);
        vo.setActivityId(IDGenerator.nextSnowId());
        vo.setCreatedAt(now);
        vo.setUpdatedAt(now);
        vo.setDiscounts(buildDiscounts(param, vo.getActivityId(), now));
        mongoTemplate.insert(vo, KylinGroupBuyActivityVo.class.getSimpleName());
        sendReplaceActivitySql(vo);
        cacheActivity(vo);
        return ResponseDto.success(vo);
    }

    @Override
    public ResponseDto<KylinGroupBuyActivityVo> update(KylinGroupBuyActivityParam param) {
        KylinGroupBuyActivityVo old = findActivity(param.getActivityId());
        if (old == null) {
            return ResponseDto.failure("拼团活动不存在");
        }
        ResponseDto<Boolean> validate = validateParam(param);
        if (!validate.isSuccess()) {
            return ResponseDto.failure(validate.getMessage());
        }
        String now = now();
        KylinGroupBuyActivityVo vo = buildActivity(param, now);
        vo.setActivityId(old.getActivityId());
        vo.setStatus(old.getStatus());
        vo.setCreatedAt(old.getCreatedAt());
        vo.setUpdatedAt(now);
        vo.setDiscounts(buildDiscounts(param, old.getActivityId(), now));
        replaceActivity(vo);
        sendReplaceActivitySql(vo);
        return ResponseDto.success(vo);
    }

    @Override
    public ResponseDto<Boolean> delete(String activityId) {
        KylinGroupBuyActivityVo old = findActivity(activityId);
        if (old == null) {
            return ResponseDto.failure("拼团活动不存在");
        }
        if (GroupBuyStatusConst.ActivityStatus.ACTIVE.equals(old.getStatus())) {
            return ResponseDto.failure("进行中的拼团活动不可删除");
        }
        mongoTemplate.remove(Query.query(Criteria.where("activityId").is(activityId)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        sendDeleteActivitySql(activityId);
        clearActivityCache(old);
        return ResponseDto.success(true);
    }

    @Override
    public ResponseDto<Boolean> toggle(String activityId, Integer targetStatus) {
        KylinGroupBuyActivityVo old = findActivity(activityId);
        if (old == null) {
            return ResponseDto.failure("拼团活动不存在");
        }
        if (!GroupBuyStatusConst.ActivityStatus.ACTIVE.equals(targetStatus)
                && !GroupBuyStatusConst.ActivityStatus.CLOSED.equals(targetStatus)) {
            return ResponseDto.failure("仅支持启用或关闭活动");
        }
        Integer fromStatus = old.getStatus();
        if (GroupBuyStatusConst.ActivityStatus.ACTIVE.equals(targetStatus)
                && !GroupBuyStatusConst.ActivityStatus.PENDING.equals(fromStatus)
                && !GroupBuyStatusConst.ActivityStatus.CLOSED.equals(fromStatus)) {
            return ResponseDto.failure("仅待启动或已关闭的活动可启用");
        }
        if (GroupBuyStatusConst.ActivityStatus.CLOSED.equals(targetStatus)
                && !GroupBuyStatusConst.ActivityStatus.ACTIVE.equals(fromStatus)) {
            return ResponseDto.failure("仅进行中的活动可关闭");
        }
        old.setStatus(targetStatus);
        old.setUpdatedAt(now());
        replaceActivity(old);
        sendUpdateActivityStatusSql(old.getActivityId(), fromStatus, targetStatus,
                DateUtil.Formatter.yyyyMMddHHmmss.parse(old.getUpdatedAt()));
        return ResponseDto.success(true);
    }

    @Override
    public ResponseDto<List<KylinGroupBuyOrderVo>> orders(String activityId, Integer status, Integer page, Integer size) {
        Criteria criteria = Criteria.where("activityId").is(activityId);
        if (status != null) {
            criteria.and("status").is(status);
        }
        Query query = Query.query(criteria).with(Sort.by(Sort.Direction.DESC, "createdAt"));
        if (page != null && size != null && page > 0 && size > 0) {
            query.skip((long) (page - 1) * size).limit(size);
        }
        return ResponseDto.success(mongoTemplate.find(query,
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName()));
    }

    private ResponseDto<Boolean> validateParam(KylinGroupBuyActivityParam param) {
        if (param.getEndTime().isBefore(param.getStartTime()) || param.getEndTime().isEqual(param.getStartTime())) {
            return ResponseDto.failure("活动结束时间必须晚于开始时间");
        }
        if (GroupBuyStatusConst.LaunchType.SCHEDULED.equals(param.getLaunchType()) && param.getScheduledTime() == null) {
            return ResponseDto.failure("定时启动必须填写启动时间");
        }
        KylinPerformanceVo performanceVo = redisSlimeUtils.getPerformanceVo(param.getPerformanceId());
        if (performanceVo == null || CollectionUtils.isEmpty(performanceVo.getTicketTimeList())) {
            return ResponseDto.failure("演出不存在");
        }
        if (param.getMinGroupSize() == null || param.getMinGroupSize() < 2) {
            return ResponseDto.failure("成团人数不能小于2");
        }
        if (param.getMaxBuyPerUser() == null || param.getMaxBuyPerUser() < 1) {
            return ResponseDto.failure("限购数量不能小于1");
        }
        if (!CollectionUtils.isEmpty(param.getDiscounts())) {
            for (KylinGroupBuyActivityParam.DiscountParam discount : param.getDiscounts()) {
                if (!ticketExists(performanceVo, discount.getTimeId(), discount.getTicketId())) {
                    return ResponseDto.failure("优惠配置的场次或票档不存在");
                }
                if (discount.getDiscountValue() == null || discount.getDiscountValue().compareTo(BigDecimal.ZERO) <= 0) {
                    return ResponseDto.failure("优惠值必须大于0");
                }
            }
        }
        return ResponseDto.success(true);
    }

    private boolean ticketExists(KylinPerformanceVo performanceVo, String timeId, String ticketId) {
        for (KylinTicketTimesVo timeVo : performanceVo.getTicketTimeList()) {
            if (!timeId.equals(timeVo.getTimeId()) || CollectionUtils.isEmpty(timeVo.getTicketList())) {
                continue;
            }
            for (KylinTicketVo ticketVo : timeVo.getTicketList()) {
                if (ticketId.equals(ticketVo.getTicketsId())) {
                    return true;
                }
            }
        }
        return false;
    }

    private KylinGroupBuyActivityVo buildActivity(KylinGroupBuyActivityParam param, String now) {
        KylinGroupBuyActivityVo vo = new KylinGroupBuyActivityVo();
        vo.setName(param.getName());
        vo.setPerformanceId(param.getPerformanceId());
        vo.setMinGroupSize(param.getMinGroupSize());
        vo.setMaxBuyPerUser(param.getMaxBuyPerUser());
        vo.setStartTime(DateUtil.Formatter.yyyyMMddHHmmss.format(param.getStartTime()));
        vo.setEndTime(DateUtil.Formatter.yyyyMMddHHmmss.format(param.getEndTime()));
        vo.setLaunchType(param.getLaunchType());
        if (param.getScheduledTime() != null) {
            vo.setScheduledTime(DateUtil.Formatter.yyyyMMddHHmmss.format(param.getScheduledTime()));
        }
        vo.setDescription(param.getDescription());
        if (GroupBuyStatusConst.LaunchType.IMMEDIATE.equals(param.getLaunchType())) {
            vo.setStatus(GroupBuyStatusConst.ActivityStatus.ACTIVE);
        } else {
            vo.setStatus(GroupBuyStatusConst.ActivityStatus.PENDING);
        }
        vo.setUpdatedAt(now);
        return vo;
    }

    private List<KylinGroupBuyDiscountVo> buildDiscounts(KylinGroupBuyActivityParam param, String activityId, String now) {
        List<KylinGroupBuyDiscountVo> list = new ArrayList<>();
        for (KylinGroupBuyActivityParam.DiscountParam discountParam : param.getDiscounts()) {
            KylinGroupBuyDiscountVo vo = new KylinGroupBuyDiscountVo();
            vo.setDiscountId(IDGenerator.nextSnowId());
            vo.setActivityId(activityId);
            vo.setPerformanceId(param.getPerformanceId());
            vo.setTimeId(discountParam.getTimeId());
            vo.setTicketId(discountParam.getTicketId());
            vo.setDiscountType(discountParam.getDiscountType());
            vo.setDiscountValue(discountParam.getDiscountValue());
            vo.setCreatedAt(now);
            list.add(vo);
        }
        return list;
    }

    private KylinGroupBuyActivityVo findActivity(String activityId) {
        Object obj = redisUtil.get(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activityId));
        if (obj != null) {
            return (KylinGroupBuyActivityVo) obj;
        }
        KylinGroupBuyActivityVo activity = mongoTemplate.findOne(Query.query(Criteria.where("activityId").is(activityId)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        cacheActivity(activity);
        return activity;
    }

    private void replaceActivity(KylinGroupBuyActivityVo activity) {
        clearActivityCache(activity);
        Query query = Query.query(Criteria.where("activityId").is(activity.getActivityId()));
        BasicDBObject updateObj = ObjectUtil.cloneBasicDBObject()
                .append("$set", mongoTemplate.getConverter().convertToMongoType(activity));
        mongoTemplate.getCollection(KylinGroupBuyActivityVo.class.getSimpleName())
                .updateOne(query.getQueryObject(), updateObj);
        cacheActivity(activity);
    }

    private void sendReplaceActivitySql(KylinGroupBuyActivityVo activity) {
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> activityDel = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> activityInsert = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> discountDel = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> discountInsert = CollectionUtil.linkedListObjectArr();

        sqls.add(SqlMapping.get("kylin_group_buy_activity.delete"));
        activityDel.add(new Object[]{activity.getActivityId()});
        sqls.add(SqlMapping.get("kylin_group_buy_activity.insert"));
        activityInsert.add(activityInsertObject(activity));
        sqls.add(SqlMapping.get("kylin_group_buy_discount.delete_by_activity"));
        discountDel.add(new Object[]{activity.getActivityId()});
        if (!CollectionUtils.isEmpty(activity.getDiscounts())) {
            sqls.add(SqlMapping.get("kylin_group_buy_discount.insert"));
            for (KylinGroupBuyDiscountVo discount : activity.getDiscounts()) {
                discountInsert.add(discountInsertObject(discount));
            }
            queueUtil.sendMsgByRedis(MQConst.SlimeQueue.SQL_SLIME_FIELD.getKey(),
                    SqlMapping.gets(sqls, activityDel, activityInsert, discountDel, discountInsert));
        } else {
            queueUtil.sendMsgByRedis(MQConst.SlimeQueue.SQL_SLIME_FIELD.getKey(),
                    SqlMapping.gets(sqls, activityDel, activityInsert, discountDel));
        }
    }

    private void sendDeleteActivitySql(String activityId) {
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> activityDel = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> discountDel = CollectionUtil.linkedListObjectArr();
        sqls.add(SqlMapping.get("kylin_group_buy_activity.delete"));
        activityDel.add(new Object[]{activityId});
        sqls.add(SqlMapping.get("kylin_group_buy_discount.delete_by_activity"));
        discountDel.add(new Object[]{activityId});
        queueUtil.sendMsgByRedis(MQConst.SlimeQueue.SQL_SLIME_FIELD.getKey(),
                SqlMapping.gets(sqls, activityDel, discountDel));
    }

    private void sendUpdateActivityStatusSql(String activityId, Integer fromStatus, Integer toStatus, LocalDateTime updatedAt) {
        queueUtil.sendMsgByRedis(MQConst.SlimeQueue.SQL_SLIME_FIELD.getKey(),
                SqlMapping.get("kylin_group_buy_activity.update_status",
                        toStatus, updatedAt, activityId, fromStatus));
    }

    private Object[] activityInsertObject(KylinGroupBuyActivityVo vo) {
        return new Object[]{
                vo.getActivityId(), vo.getName(), vo.getPerformanceId(), vo.getMinGroupSize(), vo.getMaxBuyPerUser(),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getStartTime()),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getEndTime()),
                vo.getLaunchType(),
                vo.getScheduledTime() == null ? null : DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getScheduledTime()),
                vo.getStatus(), vo.getDescription(),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getCreatedAt()),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getUpdatedAt())
        };
    }

    private Object[] discountInsertObject(KylinGroupBuyDiscountVo vo) {
        return new Object[]{
                vo.getDiscountId(), vo.getActivityId(), vo.getPerformanceId(), vo.getTimeId(), vo.getTicketId(),
                vo.getDiscountType(), vo.getDiscountValue(), DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getCreatedAt())
        };
    }

    private void cacheActivity(KylinGroupBuyActivityVo activity) {
        if (activity == null) {
            return;
        }
        redisUtil.set(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activity.getActivityId()), activity, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
    }

    private void clearActivityCache(KylinGroupBuyActivityVo activity) {
        if (activity == null) {
            return;
        }
        redisUtil.del(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activity.getActivityId()));
    }

    private String now() {
        return DateUtil.Formatter.yyyyMMddHHmmss.format(LocalDateTime.now());
    }
}
