package com.izouma.nineth.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.config.SysConstants;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.UserRankDTO;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.utils.DateTimeUtils;
import com.izouma.nineth.utils.JpaUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class ActivityCollectionService {

    private final ActivityCollectionRepo activityCollectionRepo;
    private final RedisTemplate<String, Object> redisTemplate;
    private final AwardActivityCollectionService awardActivityCollectionService;
    private final UserRepo userRepo;
    private ActivityInviteRepo activityInviteRepo;
    private InviteDateRepo inviteDateRepo;
    private final AssetRepo assetRepo;
    private final OrderRepo orderRepo;
    private final SysConfigService sysConfigService;
    private final AwardActivityCollectionInfoRepo activityCollectionInfoRepo;

    public Page<ActivityCollection> all(PageQuery pageQuery) {
        Page<ActivityCollection> pageData = activityCollectionRepo.findAll(JpaUtils.toSpecification(pageQuery, ActivityCollection.class), JpaUtils.toPageRequest(pageQuery));
        pageData.forEach((activityCollection) -> {
            if (activityCollection.getType() == null) {
                activityCollection.setType(ActivityCollectionType.DEFAULT);
            }
            //如果是普通的进行数据兼容处理 转换为列表 用于统一判断是否 可兑换藏品
            if (ActivityCollectionType.DEFAULT.equals(activityCollection.getType())) {
                exportData2List(activityCollection);
            }
        });
        return pageData;
    }

    /**
     * 将默认数据 添加到List 以方便前端统一处理
     *
     * @param activityCollection 活动集合
     */
    private void exportData2List(ActivityCollection activityCollection) {
        ActivityCollectionInfo info = ActivityCollectionInfo.builder()
                .num(activityCollection.getNum())
                .collectionId(activityCollection.getCollectionId())
                .collectionName(activityCollection.getCollectionName()).build();
        List<ActivityCollectionInfo> activities = new ArrayList<>();
        activities.add(info);
        activityCollection.setActivities(activities);
        AwardActivityCollectionInfo award = AwardActivityCollectionInfo.builder()
                .num(activityCollection.getTotal())
                .stock(activityCollection.getStock())
                //DEFAULT模式默认为可兑换1个
                .exchangeNum(1)
                .collectionId(activityCollection.getAwardCollectionId())
                .collectionName(activityCollection.getAwardCollectionName())
                .stock(activityCollection.getStock()).build();
        List<AwardActivityCollectionInfo> awards = new ArrayList<>();
        awards.add(award);
        activityCollection.setAwardList(awards);
    }

    public synchronized Integer getStock(Long id, Long collectionId) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(genRedisKey(id, collectionId));
        if (ops.get() == null) {
            Integer stock = getStockFromMysql(id, collectionId);
            Boolean success = ops.setIfAbsent(Optional.ofNullable(stock)
                    .orElse(0), 7, TimeUnit.DAYS);
            log.info("创建redis铸造活动库存：{}", success);
        }
        return (Integer) ops.get();
    }

    /**
     * 从数据库中获取库存
     *
     * @param id           活动ID
     * @param collectionId 藏品ID
     * @return 数量
     */
    private synchronized Integer getStockFromMysql(Long id, Long collectionId) {
        Integer stock;
        ActivityCollection activityCollection = activityCollectionRepo.findById(id).orElseThrow(new BusinessException("藏品不存在"));
        if (activityCollection.getType() == null
                || activityCollection.getType().equals(ActivityCollectionType.DEFAULT)) {
            stock = activityCollection.getStock();
        } else {
            stock = awardActivityCollectionService.getStock(id, collectionId);
        }
        return stock;
    }

    /**
     * 获取RedisKey
     *
     * @param id           收集活动ID
     * @param collectionId 集合ID
     * @return Redis Key
     */
    private String genRedisKey(Long id, Long collectionId) {
        return RedisKeys.MINT_ACTIVITY_STOCK + id + RedisKeys.COLLECTION + collectionId;
    }

    /**
     * 库存减去指定数量
     *
     * @param id           收集活动ID
     * @param collectionId 藏品ID
     * @param number       数量
     * @return 库存
     */
    public Long decreaseStock(Long id, Long collectionId, int number) {
        return increaseStock(id, collectionId, -number);
    }

    public Long increaseStock(Long id, Long collectionId, int number) {
        synchronized (id.toString().intern()) {
            BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(genRedisKey(id, collectionId));
            if (ops.get() == null) {
                Boolean success = ops.setIfAbsent(Optional.ofNullable(activityCollectionRepo.getStock(id))
                        .orElse(0), 7, TimeUnit.DAYS);
                log.info("创建redis铸造活动库存：{}", success);
            }
            Long stock = ops.increment(number);
            //不做数据库同步，以数据库记录为准
            //rocketMQTemplate.convertAndSend(generalProperties.getUpdateActivityStockTopic(), id);
            return stock;
        }

    }


    public ActivityCollection save(ActivityCollection input) {
        //有ID执行更新图片操作
        if (input.getId() != null) {
            ActivityCollection orig = activityCollectionRepo.findById(input.getId())
                    .orElseThrow(new BusinessException("活动无记录"));
            orig.setPic(input.getPic());
            orig.setDescription(input.getDescription());
            orig.setStartTime(input.getStartTime());
            return activityCollectionRepo.save(orig);
        }

        Set<Long> activitiesIds = input.getActivities().stream().map(ActivityCollectionInfo::getCollectionId).collect(Collectors.toSet());
        Set<Long> awardActivitiesIds = input.getAwardList().stream().map(AwardActivityCollectionInfo::getCollectionId).collect(Collectors.toSet());
        HashSet<Long> result = new HashSet<>(activitiesIds);
        result.retainAll(awardActivitiesIds);
        if (!result.isEmpty()) {
            throw new BusinessException("兑换的藏品不能和领取的藏品相同");
        }
        //集齐任意数量可兑换 和 集齐指定数量+任意藏品可兑换
        if (ActivityCollectionType.ANY_TO_ONE.equals(input.getType()) || ActivityCollectionType.SOME_TO_ANY.equals(input.getType())) {
            input.getActivities().stream().filter(ActivityCollectionInfo::getMustNeedCollected).forEach(activityCollectionInfo -> {
                if (activityCollectionInfo.getNum() == 0) {
                    throw new BusinessException("必须兑换的藏品要求兑换数量不能为0");
                }
            });
        }
        //如果不是全平台则修改为
        if (!ActivityCollectionType.SOME_TO_ANY.equals(input.getType()) && (activitiesIds.size() == 1 && awardActivitiesIds.size() == 1)) {
            input.setCollectionName(input.getActivities().get(0).getCollectionName());
            input.setNum(input.getActivities().get(0).getNum());
            input.setCollectionId(input.getActivities().get(0).getCollectionId());
            //取兑换列表数据
            input.setAwardCollectionName(input.getAwardList().get(0).getCollectionName());
            input.setAwardCollectionId(input.getAwardList().get(0).getCollectionId());
            input.setTotal(input.getAwardList().get(0).getNum());
            input.setStock(input.getTotal());
            input.getAwardList().forEach(item -> {
                item.setStock(item.getNum());
                if (item.getAirDropType() == null){
                    item.setAirDropType(AirDropType.asset);
                }
            });
        } else {
            //计算total
            int sum = input.getAwardList().stream().mapToInt(AwardActivityCollectionInfo::getNum).sum();
            input.setTotal(sum);
            input.getAwardList().forEach(item -> {
                item.setStock(item.getNum());
                if (item.getAirDropType() == null) {
                    item.setAirDropType(AirDropType.asset);
                }
            });
        }
        return activityCollectionRepo.save(input);
    }

    /**
     * 排行版查询
     *
     * @param type
     * @param start
     * @param count
     * @param projectId
     * @return
     */
    public List<UserRankDTO> getRanking(int type, int start, int count, int projectId) {
        String suffix = "" + type + "_" + projectId + "_" + count;
        String redisCacheKey = RedisKeys.RANKING_DATA_CACHE + suffix;
        if (!(type == 1 || type == 2 || type == 3 || type == 4 || type == 5 || type == 6)) {
            throw new BusinessException("请求排行榜有误");
        }

        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        List<UserRankDTO> cacheData = (List<UserRankDTO>) ops.get(redisCacheKey);
        if (cacheData != null) {
            //返回缓存数据
            return cacheData;
        }

        List<UserRankDTO> userRankVos = new ArrayList<>();
        Map<Long, Number> rankMap = new HashMap<>();
        List<OrderRanking> orderRankingList = new ArrayList<>();
        switch (type) {
            case 1:
                ActivityInvite activityInvite = activityInviteRepo.findRecent();
                if (activityInvite == null) {
                    return userRankVos;
                }
                //根据活动ID查询邀请数据
                List<Map<String, Object>> inviteDateList = inviteDateRepo.findActivityIdDateGroup(activityInvite.getId(), InvitationNote.SUCCESSFULLY.ordinal());
                //邀请排行榜
                inviteDateList.forEach((item) -> {
                    Long inviteUserId = Long.valueOf(item.get("inviter_user_id").toString());
                    Integer num = Integer.valueOf(item.get("num").toString());
                    rankMap.put(inviteUserId, num);
                });
                //后台数据补充
                replenish(rankMap, SysConstants.FALSE_DATA_PHONE_NUMBER, SysConstants.FALSE_DATA, false);
                break;
            case 2:
                List<Map<String, BigDecimal>> object = orderRepo.salesStatistics2(OrderStatus.FINISH.name());
                object.forEach(e -> orderRankingList.add(new OrderRanking(Long.parseLong(e.get("user_id") + ""), new BigDecimal(e.get("count") + ""))));
                orderRankingList.forEach(e -> rankMap.put(e.userId, e.count.doubleValue()));
                break;
            case 3:
                LocalDateTime starTtime = DateTimeUtils.getDateTimeOfTimestamp(sysConfigService.getDateTime(SysConstants.RANKING_ACTIVITY_START_TIME));
                LocalDateTime endTime = DateTimeUtils.getDateTimeOfTimestamp(sysConfigService.getDateTime(SysConstants.RANKING_ACTIVITY_END_TIME));
                List<Map<String, BigDecimal>> item = orderRepo.salesStatistics1(OrderStatus.FINISH.name(), starTtime, endTime);
                item.forEach(e -> orderRankingList.add(new OrderRanking(Long.parseLong(e.get("user_id") + ""), new BigDecimal(e.get("count") + ""))));
                orderRankingList.forEach(e -> rankMap.put(e.userId, e.count.doubleValue()));
                //后台数据补充
                replenish(rankMap, SysConstants.CONSUME_PHONE, SysConstants.CONSUME_DATA, true);
                break;
            case 4:
                LocalDateTime currentTime = DateTimeUtils.getDateTimeOfTimestamp(System.currentTimeMillis());
                //今天9：00：00
                LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).plusHours(9);
                //明天9：00：00
                LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).plusHours(24 + 9);
                //如果当前时间是在9：00：00之前那还是按照昨天的排行榜时间算
                if (!currentTime.isAfter(todayStart)) {
                    //00：00：00点减去15个小时等于前一天9：00：00
                    todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).plusHours(-15);
                    ;//今天9：00：00
                    todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MIN).plusHours(9);
                }
                List<Map<String, BigDecimal>> temp = orderRepo.salesStatistics3(OrderStatus.FINISH.name(), todayStart, todayEnd);
                temp.forEach(e -> orderRankingList.add(new OrderRanking(e.get("name") + "", new BigDecimal(e.get("count") + ""), e.get("pic") + "")));
                break;
            case 5:
                ActivityInvite invites = activityInviteRepo.findRecent();
                if (invites == null) {
                    return userRankVos;
                }
                List<Map<String, Object>> userIinvitorList = userRepo.userIinvitor(invites.getInviteStartdatetime(), AuthStatus.SUCCESS.name());
                //实名认证邀请排行榜
                userIinvitorList.forEach((invite) -> {
                    Long inviteUserId = Long.valueOf(invite.get("userid").toString());
                    Integer num = Integer.valueOf(invite.get("num").toString());
                    rankMap.put(inviteUserId, num);
                });
                break;
            //奖品排行榜
            case 6:
                List<Map<String, Object>> ownerScore = assetRepo.ownerScoreTop30();
                ownerScore.forEach(score -> {
                    long id = Long.parseLong(score.get("user_id").toString());
                    BigDecimal totalScore = (BigDecimal) score.get("total_score");
                    rankMap.put(id, totalScore.longValue());
                });
                break;
            default:
                throw new BusinessException("请求排行榜有误");

        }
        //补充用户信息 type4 需要补充额外信息 其余走默认逻辑
        if (type == 4) {
            for (OrderRanking orderRanking : orderRankingList) {
                UserRankDTO userRankVo = new UserRankDTO();
                try {
                    BigDecimal bigDecimal = Objects.requireNonNullElse(orderRanking.count, BigDecimal.ZERO);
                    BigDecimal amount = bigDecimal.setScale(2, RoundingMode.DOWN);
                    userRankVo.setAmount(amount);
                    JSONArray jsonArray = JSON.parseArray(orderRanking.pic);
                    JSONObject obj = (JSONObject) jsonArray.get(0);
                    userRankVo.setAvatar(obj.getString("url"));
                    userRankVo.setNickname(orderRanking.name);
                    userRankVos.add(userRankVo);
                } catch (Exception e) {
                    log.info("金榜藏品报错{},{},{}", orderRanking.name, orderRanking.pic, orderRanking.count, e);
                }
            }
        } else {
            for (Map.Entry<Long, Number> entry : rankMap.entrySet()) {
                Long userId = entry.getKey();
                Number value = entry.getValue();
                if (value == null || value.doubleValue() <= 0) {
                    continue;
                }
                Optional<User> tmp = userRepo.findById(userId);
                if (tmp.isPresent()) {
                    UserRankDTO userRankVo = new UserRankDTO();
                    userRankVo.setUser_id(userId);
                    BigDecimal bigDecimal = BigDecimal.valueOf(value.doubleValue());
                    BigDecimal amount = bigDecimal.setScale(type == 1 || type == 5 ? 0 : 2, RoundingMode.DOWN);
                    userRankVo.setAmount(amount);
                    userRankVo.setAvatar(tmp.get().getAvatar());
                    userRankVo.setNickname(tmp.get().getNickname());
                    userRankVos.add(userRankVo);

                }
            }
        }
        //排序
        int rank = start + 1;
        Collections.sort(userRankVos);
        Collections.reverse(userRankVos);
        if (userRankVos.size() > 30) {
            userRankVos = new ArrayList<>(userRankVos.subList(0, 30));
        }
        for (UserRankDTO userRankVo : userRankVos) {
            userRankVo.setRank(rank);
            rank++;
        }

        ops.set(redisCacheKey, userRankVos, 1800, TimeUnit.SECONDS);
        return userRankVos;
    }

    private void replenish(Map<Long, Number> rankMap, String phonesKey, String datesKey, boolean schema) {
        try {
            String[] phones = sysConfigService.getString(phonesKey).split(",");
            String[] dates = sysConfigService.getString(datesKey).split(",");
            for (int i = 0; i < phones.length; i++) {
                Optional<User> tmp = userRepo.findByPhone(phones[i]);
                if (tmp.isPresent()) {
                    if (schema) {
                        rankMap.put(tmp.get().getId(), rankMap.getOrDefault(tmp.get().getId(), 0).intValue() + Integer.parseInt(dates[i]));
                    } else {
                        rankMap.put(tmp.get().getId(), Integer.valueOf(dates[i]));
                    }
                }
            }
        } catch (Exception e) {
            log.info("邀请排行榜需要造假数据配置有误", e);
        }
    }


    public ActivityCollection getById(Long id) {
        ActivityCollection activityCollection = activityCollectionRepo.findById(id).orElseThrow(new BusinessException("活动无记录"));
        if (ActivityCollectionType.DEFAULT.equals(activityCollection.getType())) {
            exportData2List(activityCollection);
            activityCollection.setType(ActivityCollectionType.ONE_TO_MANY);
        }
        return activityCollection;
    }

    /**
     * 检测用户是否持有足够库存
     *
     * @param activityCollection
     * @param user
     * @return
     */
    public boolean userCollected(ActivityCollection activityCollection, User user) {
        boolean collected = false;
        switch (activityCollection.getType()) {
            case DEFAULT:
            case ONE_TO_MANY:
                for (ActivityCollectionInfo item : activityCollection.getActivities()) {
                    item.setUserHoldNum(assetRepo.countAssetHeld(user.getId(), item.getCollectionId(), activityCollection.getProjectId()));
                    if (item.getUserHoldNum() >= item.getNum() && activityCollection.getStock() >= 1) {
                        collected = true;
                    }
                }
                break;
            case MANY_TO_MANY:
                collected = true;
                for (ActivityCollectionInfo item : activityCollection.getActivities()) {
                    item.setUserHoldNum(assetRepo.countAssetHeld(user.getId(), item.getCollectionId(), activityCollection.getProjectId()));
                    if (item.getUserHoldNum() < item.getNum() || activityCollection.getStock() <= 0) {
                        collected = false;
                    }
                }
                break;
            case ANY_TO_ONE:
                for (ActivityCollectionInfo item : activityCollection.getActivities()) {
                    item.setUserHoldNum(assetRepo.countAssetHeld(user.getId(), item.getCollectionId(), activityCollection.getProjectId()));
                }
                int sum = activityCollection.getActivities().stream().mapToInt(ActivityCollectionInfo::getUserHoldNum).sum();
                int needCollectionNum = activityCollection.getActivities().stream()
                        .mapToInt(ActivityCollectionInfo::getNum).sum();
                List<ActivityCollectionInfo> collect = activityCollection.getActivities().stream()
                        .filter(ActivityCollectionInfo::getMustNeedCollected).collect(Collectors.toList());
                int mustCollectedNum = collect.stream()
                        .mapToInt(ActivityCollectionInfo::getUserHoldNum).sum();
                int mustCollectedNeedNum = collect.stream()
                        .mapToInt(ActivityCollectionInfo::getNum).sum();
                collected = (mustCollectedNum >= mustCollectedNeedNum) && (sum >= needCollectionNum) && activityCollection.getStock() > 0;
                break;
            case SOME_TO_ANY:
                List<ActivityCollectionInfo> mustCollected = activityCollection.getActivities().stream().filter(ActivityCollectionInfo::getMustNeedCollected).collect(Collectors.toList());
                for (ActivityCollectionInfo item : mustCollected) {
                    item.setUserHoldNum(assetRepo.countAssetHeld(user.getId(), item.getCollectionId(), activityCollection.getProjectId()));
                }
                int num = mustCollected.stream()
                        .mapToInt(ActivityCollectionInfo::getUserHoldNum).sum();
                int needNum = mustCollected.stream()
                        .mapToInt(ActivityCollectionInfo::getNum).sum();
                Integer userHoldNum = assetRepo.findAssetNormalCount(user.getId());

                collected = (num >= needNum) && (userHoldNum >= activityCollection.getNeedCollectedNum()) && activityCollection.getStock() > 0;
                break;
            default:
                throw new BusinessException("未知的活动模式");
        }
        return collected;
    }

    /**
     * 当前活动 是否在兑换期间
     *
     * @param collectionId 上架的藏品Id
     * @return 是否在兑换期间
     */
    public boolean isOnActivity(Long collectionId) {
        Long activityId = awardActivityCollectionService.findLastActivityByCollectionId(collectionId);
        if (activityId == null) {
            return false;
        }
        ActivityCollection activityCollection = activityCollectionRepo.findById(activityId).orElse(null);
        if (activityCollection == null || activityCollection.getEndTime() == null) {
            return false;
        }
        return !LocalDateTime.now().isAfter(activityCollection.getEndTime());
    }

    public void softDelete(Long id) {
        activityCollectionRepo.softDelete(id);
        awardActivityCollectionService.softDeleteByActivityId(id);
        activityCollectionInfoRepo.softDeleteByActivityId(id);
    }
}
