package com.izouma.nineth.service.rewardpoints;

import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.config.SysConstants;
import com.izouma.nineth.domain.Collection;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.domain.DrawLotteryHistory;
import com.izouma.nineth.domain.PointActivityAward;
import com.izouma.nineth.domain.PointRewardActivity;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.SupplyLotteryDTO;
import com.izouma.nineth.enums.AirDropType;
import com.izouma.nineth.enums.OperationType;
import com.izouma.nineth.enums.rewardpoints.AwardDrawStatus;
import com.izouma.nineth.enums.rewardpoints.DrawType;
import com.izouma.nineth.enums.rewardpoints.PointActivityAwardType;
import com.izouma.nineth.enums.rewardpoints.PointRewardActivityViewType;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.lock.RedisLockable;
import com.izouma.nineth.repo.CollectionRepo;
import com.izouma.nineth.repo.UserRepo;
import com.izouma.nineth.repo.rewardpoints.PointRewardActivityRepo;
import com.izouma.nineth.service.AirDropService;
import com.izouma.nineth.service.SysConfigService;
import com.izouma.nineth.utils.JpaUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.Range;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 积分活动Service
 */
@Slf4j
@Service
@AllArgsConstructor
public class PointActivityService {
    private final PointRewardActivityRepo pointRewardActivityRepo;
    private final PointActivityAwardService pointActivityAwardService;
    private final DrawLotteryHistoryService drawLotteryHistoryService;
    private final UserIntegralService userIntegralService;
    private final UserIntegralChangeLogService userIntegralChangeLogService;
    private final AirDropService airDropService;
    private final CollectionRepo collectionRepo;
    private final SysConfigService sysConfigService;

    private final UserRepo userRepo;

    /**
     * 添加积分兑换活动
     */
    @Transactional
    public PointRewardActivity save(PointRewardActivity input) {
        if (PointRewardActivityViewType.SUDOKU.equals(input.getViewType()) && input.getAwards().size() != 8) {
            throw new BusinessException("九宫格奖项数量配置数量必须是8个,请检查");
        }
        //有ID执行更新开始结束时间操作
        if (input.getId() != null) {
            PointRewardActivity activity = pointRewardActivityRepo.findById(input.getId())
                    .orElseThrow(new BusinessException("积分活动无记录"));
            activity.setStartTime(input.getStartTime());
            activity.setEndTime(input.getEndTime());
            return pointRewardActivityRepo.save(activity);
        }
        PointRewardActivity activity = pointRewardActivityRepo.save(input);
        for (PointActivityAward award : activity.getAwards()) {
            award.setActivityId(activity.getId());
            award.setStock(award.getNum());
        }
        pointActivityAwardService.saveAwards(activity.getAwards());
        return activity;
    }

    public Page<PointRewardActivity> all(PageQuery pageQuery) {
        return pointRewardActivityRepo.findAll(JpaUtils.toSpecification(pageQuery, PointRewardActivity.class), JpaUtils.toPageRequest(pageQuery));
    }

    @RedisLockable(prefix = RedisKeys.POINT_DRAW_LOCK, key = "#activityId", isWaiting = true, retryCount=10)
    @Transactional
    public PointActivityAward executeDraw(Long activityId, User user,PointRewardActivity activity) {

        List<PointActivityAward> awards = pointActivityAwardService.activityAwards(activityId);
        Map<PointActivityAward, Range<Integer>> randomRange = new HashMap<>();
        int c = 0, sum = 0;
        for (PointActivityAward item : awards) {
            randomRange.put(item, Range.between(c, c + item.getStock()));
            c += item.getStock();
            sum += item.getStock();
        }
        if (sum ==0){
            throw new BusinessException("当前积分抽奖活动库存不足");
        }
        log.info("开始积分抽奖，活动ID:{}, 库存数量：{}", activityId, sum);
        int retry = 0;
        PointActivityAward winItem = null;

        while (winItem == null) {
            retry++;
            int rand = RandomUtils.nextInt(0, sum + 1);
            for (Map.Entry<PointActivityAward, Range<Integer>> entry : randomRange.entrySet()) {
                PointActivityAward item = entry.getKey();
                Range<Integer> range = entry.getValue();
                if (rand >= range.getMinimum() && rand < range.getMaximum()) {
                    int total = awards.stream().filter(i -> !i.isRare())
                            .mapToInt(PointActivityAward::getNum).sum();
                    int stock = awards.stream().filter(i -> !i.isRare())
                            .mapToInt(PointActivityAward::getStock).sum();
                    if (item.isRare()) {
                        double nRate = stock / (double) total;
                        double rRate = (item.getStock() - 1) / (double) item.getNum();

                        if (Math.abs(nRate - rRate) < (1 / (double) item.getNum()) || retry > 1 || rRate == 0) {
                            if (!(nRate > 0.1 && item.getStock() == 1)) {
                                winItem = item;
                            }
                        }
                    } else {
                        double nRate = (stock - 1) / (double) total;
                        double rRate = item.getStock() / (double) item.getNum();

                        if (Math.abs(nRate - rRate) < 0.2 || retry > 1 || nRate == 0) {
                            winItem = item;
                        }
                    }
                }
            }

            if (retry > 100 && winItem == null) {
                throw new BusinessException("积分抽奖抽取失败,剩余数量：" + sum);
            }
        }

        log.info("开始抽卡，盲盒ID:{}, 获得奖品为：{}", activityId, winItem.getName());
        //变更的是积分抽取抽卡库存
        pointActivityAwardService.decreaseStock(winItem.getId(), 1);

        return winItem;
    }



    /**
     * 抽奖
     * @param activityId 活动id
     * @param user 用户
     * @return 获得奖品
     */
    @RedisLockable(prefix = RedisKeys.POINT_DRAW_LOCK, key = "#activityId", isWaiting = true, retryCount=10)
    @Transactional
    public PointActivityAward drawLottery(Long activityId, User user){
        //消耗积分
        PointRewardActivity activity = pointRewardActivityRepo.findById(activityId).orElseThrow(new BusinessException("活动不存在"));
        boolean activityNotStart = activity.getStartTime() != null && activity.getStartTime().isAfter(LocalDateTime.now());
        //测试人员Id
        Long testerUserId = Integer.valueOf(sysConfigService.getInt(SysConstants.TESTER_USER_ID)).longValue();
        //判断收集活动未开始
        if (activityNotStart && !testerUserId.equals(user.getId())) {
            throw new BusinessException("当前活动还未开始");
        }
        //判断收集活动已结束
        boolean activityClosed = activity.getEndTime()!=null && LocalDateTime.now().isAfter(activity.getEndTime());
        if (activityClosed){
            throw new BusinessException("本次积分抽奖活动已结束");
        }
        Integer cost = activity.getCost();
        UserIntegral userIntegral = userIntegralService.getAndInitUserIntegral(user.getId());
        Long remain = userIntegral.getAvailableIntegral() - cost;
        if(remain < 0){
            throw new BusinessException("可用积分不足,抽奖失败");
        }

        UserIntegralChangeLog changeLog = userIntegralChangeLogService.createByDrawLottery(user.getId(), cost.longValue());

        //抽奖
        PointActivityAward activityAward = executeDraw(activityId, user,activity);
        changeLog.setSourceId(activityId);

        DrawLotteryHistory history = DrawLotteryHistory.builder()
                .userId(user.getId())
                .phone(user.getPhone())
                .userName(user.getNickname()).activityId(activityId).type(DrawType.POINT)
                .issueTime(activity.getEndTime()).status(AwardDrawStatus.NOT_RECEIVE)
                .awardName(activityAward.getName()).awardId(activityAward.getId())
                .pic(activityAward.getPic())
                .awardType(activityAward.getType())
                .build();
        drawLotteryHistoryService.save(history);
        if (PointActivityAwardType.ASSET.equals(activityAward.getType())) {
            //自动发放奖品
                history.setStatus(AwardDrawStatus.RECEIVED);
                Optional<Collection> collection = collectionRepo.findById(activityAward.getAwardCollectionId());
                airDropService.batchCreate(AirDrop.builder()
                        .name("积分抽奖奖品发放")
                        .type(AirDropType.asset)
                        .collectionId(activityAward.getAwardCollectionId())
                        .phone(Arrays.asList(user.getPhone()))
                        .userIds(Arrays.asList(user.getId()))
                        .projectId(collection.get().getProjectId())
                        .dropCount(1)
                        .operation(OperationType.DRAW.getDescription())
                        //积分抽奖 不需要在空投记录里保存
                        .notSave(true)
                        .build());
        }


        return activityAward;
    }


    public PointRewardActivity detail(Long activityId) {
        PointRewardActivity activity = pointRewardActivityRepo.findById(activityId).orElseThrow(new BusinessException("查询的积分活动不存在"));
        activity.setAwards(pointActivityAwardService.activityAwards(activityId));
        return activity;
    }

    public PointRewardActivity now() {
        Long activityId = pointRewardActivityRepo.recentActivity().orElseThrow(new BusinessException("很抱歉,当前没有正在进行的积分活动"));
        PointRewardActivity activity = detail(activityId);
        activity.getAwards().forEach(item -> {
            item.setStock(null);
            item.setNum(null);
        });
        return activity;
    }

    public List<DrawLotteryHistory> nowActivityAwards() {
        Long activityId = pointRewardActivityRepo.recentActivity().orElseThrow(new BusinessException("很抱歉,当前没有正在进行的积分活动"));
        return drawLotteryHistoryService.recent50History(activityId);
    }

    /**
     * 删除活动
     * @param id
     */
    @Transactional
    public void softDelete(Long id) {
        pointRewardActivityRepo.softDel(id);
        pointActivityAwardService.softDelByActivityId(id);
    }


    @RedisLockable(prefix = RedisKeys.POINT_DRAW_LOCK, key = "#input.activityId", isWaiting = true, retryCount=10)
    public PointActivityAward supplyAgain(SupplyLotteryDTO input) {
        PointRewardActivity activity = pointRewardActivityRepo.findById(input.getActivityId()).orElseThrow(new BusinessException("活动不存在"));
        Integer cost = activity.getCost();
        UserIntegral userIntegral = userIntegralService.getAndInitUserIntegral(input.getUserId());
        Long remain = userIntegral.getAvailableIntegral() - cost;
        if(remain < 0){
            throw new BusinessException("可用积分不足,补发失败");
        }

        User user = userRepo.findById(input.getUserId()).orElseThrow(new BusinessException("用户不存在"));
        UserIntegralChangeLog changeLog = userIntegralChangeLogService.createByDrawLottery(input.getUserId(), cost.longValue());
        changeLog.setSourceId(input.getAwardId());
        PointActivityAward activityAward = pointActivityAwardService.findAwardById(input.getAwardId());
        DrawLotteryHistory history = DrawLotteryHistory.builder()
                .userId(user.getId())
                .phone(user.getPhone())
                .userName(user.getNickname()).activityId(activity.getId()).type(DrawType.POINT)
                .issueTime(activity.getEndTime()).status(AwardDrawStatus.NOT_RECEIVE)
                .awardName(activityAward.getName())
                .awardId(activityAward.getId())
                .pic(activityAward.getPic())
                .awardType(activityAward.getType())
                .build();
        drawLotteryHistoryService.save(history);


        //自动发放奖品
        if (PointActivityAwardType.ASSET.equals(activityAward.getType())) {
            history.setStatus(AwardDrawStatus.RECEIVED);
            Optional<Collection> collection = collectionRepo.findById(activityAward.getAwardCollectionId());
            airDropService.batchCreate(AirDrop.builder()
                    .name("积分抽奖奖品发放")
                    .type(AirDropType.asset)
                    .collectionId(activityAward.getAwardCollectionId())
                    .phone(Arrays.asList(user.getPhone()))
                    .userIds(Arrays.asList(user.getId()))
                    .projectId(collection.get().getProjectId())
                    .dropCount(1)
                    .operation(OperationType.DRAW.getDescription())
                    //积分抽奖 不需要在空投记录里保存
                    .notSave(true)
                    .build());
        }

        return activityAward;
    }
}
