package com.duiba.tuia.youtui.web.bo.impl;

import cn.com.duiba.credits.sdk.*;
import cn.com.duiba.tuia.activity.center.api.constant.BalanceRecordType;
import cn.com.duiba.tuia.activity.center.api.constant.BalanceType;
import cn.com.duiba.tuia.activity.center.api.constant.ConfigKey;
import cn.com.duiba.tuia.activity.center.api.constant.PageType;
import cn.com.duiba.tuia.activity.center.api.dto.ActivityWinOrderDto;
import cn.com.duiba.tuia.activity.center.api.dto.GuidePageMoneyConfigDto;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.BalanceRecordDto;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.ConsumerDto;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.UserBalanceDto;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.req.BalanceRecordAdd;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.req.BalanceWithdrawReq;
import cn.com.duiba.tuia.activity.center.api.exception.ActivityCenterException;
import cn.com.duiba.tuia.activity.center.api.exception.ActivityCenterRuntimeException;
import cn.com.duiba.tuia.ssp.center.api.dto.RspSlotBackendDetailDto;
import cn.com.duiba.tuia.ssp.center.api.remote.RemoteSlotBackendService;
import cn.com.duiba.wolf.dubbo.DubboResult;
import com.alibaba.fastjson.JSONObject;
import com.duiba.tuia.youtui.web.bo.BalanceBO;
import com.duiba.tuia.youtui.web.constant.CacheKey;
import com.duiba.tuia.youtui.web.constant.Constants;
import com.duiba.tuia.youtui.web.constant.ErrorCode;
import com.duiba.tuia.youtui.web.exception.ActivityException;
import com.duiba.tuia.youtui.web.model.BalanceRecordVO;
import com.duiba.tuia.youtui.web.model.Result;
import com.duiba.tuia.youtui.web.service.*;
import com.duiba.tuia.youtui.web.tool.CachedKeyUtils;
import com.duiba.tuia.youtui.web.tool.JsonUtils;
import com.duiba.tuia.youtui.web.tool.PhoneTool;
import com.duiba.tuia.youtui.web.tool.RequestLocal;
import com.duiba.tuia.youtui.web.tool.ResultUtil;
import com.duiba.tuia.youtui.web.tool.WinPublicTool;
import java.io.Serializable;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Created by Administrator on 2017/12/14.
 */
@Slf4j
@Service
public class BalanceBOImpl extends BaseCacheService implements BalanceBO {

    private static final String[] template = {"0.88", "1.88", "8.8", "18.88"};

    @Autowired
    private GuidePageService guidePageService;

    @Autowired
    private UserBalanceService userBalanceService;

    @Autowired
    private BalanceRecordService balanceRecordService;

    @Autowired
    private ConsumerService consumerService;

    @Autowired
    private RemoteSlotBackendService remoteSlotBackendService;

    @Autowired
    private SystemConfigService systemConfigService;

    @Override
    public Long getReward(Long pageId, HttpServletRequest request) throws ActivityException {
        // 如果并发性过高，在最外层包裹分布式锁 - RedisLockRegistry
        // 1.得到用户信息，校验用户是否存在，包括临时用户
        Long cid = RequestLocal.get().getCid();
        Long rewardMoney = 0l;
        if (cid == null) {
            return rewardMoney;
        }
        // 2.查询零钱奖项配置
        GuidePageMoneyConfigDto guidePageMoneyConfigDto = guidePageService.getGuidePageMoneyConfigDto(pageId);
        if (guidePageMoneyConfigDto == null) {
            // 奖项配置有问题的话，直接未中奖
            return rewardMoney;
        }
        // 3.是否中奖以及中奖金额<将中奖概率放在前面，可以挡掉大部分的并发打到redis上>
        rewardMoney = rewardMoney(guidePageMoneyConfigDto);
        if (rewardMoney == 0) {
            return rewardMoney;
        }
        // 4.1 用户资格校验，活动直投页"用户每日次数上限"、"每日最大次数"校验
        // 4.2 缓存活动每日最大次数、每日限额数据，并增加用户缓存次数
        // 4.3 矫正中奖金额，用完库存
        rewardMoney = verifyReward(cid, guidePageMoneyConfigDto, rewardMoney);
        // 6.中奖后订单记录
        return addUserBalanceRecord(cid, rewardMoney, pageId);
    }

    /**
     * 所有的活动前校验
     * 频率：每个自然日
     * <p>
     * 本直投页频率
     *
     * @param cid
     * @param guidePageMoneyConfigDto
     * @return
     */
    private Long verifyReward(Long cid, GuidePageMoneyConfigDto guidePageMoneyConfigDto, Long rewardMoney) throws ActivityException {
        // 直投页每天限制金额校验
        String usedDailyLimitKey = CachedKeyUtils.getRedisKey(CacheKey.GUIDE_PAGE_USED_DAILY_LIMIT, guidePageMoneyConfigDto.getPageId());
        String usedDailyLimit = stringRedisTemplate.opsForValue().get(usedDailyLimitKey);
        if (!StringUtils.isEmpty(usedDailyLimit)) {
            // 只要剩余金额小于最小中奖金额，则不能抽奖了
            if (guidePageMoneyConfigDto.getDailyLimit() - Long.valueOf(usedDailyLimit) < Long.valueOf(guidePageMoneyConfigDto.getMinAmount())) {
                throw new ActivityException(ErrorCode.E0111003);
            }
            // 矫正中奖金额
            if (Long.valueOf(guidePageMoneyConfigDto.getDailyLimit()) - Long.valueOf(usedDailyLimit) - rewardMoney < 0) {
                rewardMoney = Long.valueOf(guidePageMoneyConfigDto.getDailyLimit()) - Long.valueOf(usedDailyLimit);
            }
        }
        // 直投页每天限制次数
        String usedDailyTimesKey = CachedKeyUtils.getRedisKey(CacheKey.GUIDE_PAGE_USED_DAILY_TIMES, cid, guidePageMoneyConfigDto.getPageId());
        String usedDailyTimes = stringRedisTemplate.opsForValue().get(usedDailyTimesKey);
        if (!StringUtils.isEmpty(usedDailyTimes) &&
                Long.valueOf(usedDailyTimes) - guidePageMoneyConfigDto.getDailyTimes() >= 0) {
            throw new ActivityException(ErrorCode.E0111002);
        }

        // 获奖key对应value自增，以及双重校验
        rewardCacheIncrement(guidePageMoneyConfigDto, rewardMoney, usedDailyLimitKey, usedDailyTimesKey);
        return rewardMoney;
    }

    /**
     * 获奖key对应value自增，以及双重校验
     *
     * @param guidePageMoneyConfigDto
     * @param rewardMoney
     * @param usedDailyLimitKey
     * @param usedDailyTimesKey
     * @throws ActivityException
     */
    private void rewardCacheIncrement(GuidePageMoneyConfigDto guidePageMoneyConfigDto, Long rewardMoney, String usedDailyLimitKey, String usedDailyTimesKey) throws ActivityException {
        // key自增以及其过期时间
        Long todayRestSeconds = 24 * 3600l - LocalTime.now().toSecondOfDay();
        Long newUsedDailyLimit = stringRedisTemplate.opsForValue().increment(usedDailyLimitKey, rewardMoney);
        if (rewardMoney.equals(newUsedDailyLimit)) {
            stringRedisTemplate.expire(usedDailyLimitKey, todayRestSeconds, TimeUnit.SECONDS);
        }
        // 双重校验，如果并发性过高，increment之后的金额大于每日总金额，直接抛出活动结束异常
        if (guidePageMoneyConfigDto.getDailyLimit() - newUsedDailyLimit < 0) {
            throw new ActivityException(ErrorCode.E0111003);
        }

        Long newUsedDailyTimes = stringRedisTemplate.opsForValue().increment(usedDailyTimesKey, 1);
        if (newUsedDailyTimes == 1) {
            stringRedisTemplate.expire(usedDailyTimesKey, todayRestSeconds, TimeUnit.SECONDS);
        }
        // 双重校验
        if (Long.valueOf(guidePageMoneyConfigDto.getDailyTimes()) - newUsedDailyTimes < 0) {
            throw new ActivityException(ErrorCode.E0111002);
        }

    }

    /**
     * 中奖概率逻辑
     *
     * @param guidePageMoneyConfigDto
     * @throws ActivityException
     */
    private Long rewardMoney(GuidePageMoneyConfigDto guidePageMoneyConfigDto) {
        return Math.random() * 100 - Double.valueOf(guidePageMoneyConfigDto.getRate()) < 0 ?
                ThreadLocalRandom.current().nextLong(guidePageMoneyConfigDto.getMinAmount(),
                        guidePageMoneyConfigDto.getMaxAmount() + 1) : 0;
    }

    /**
     * 添加用户中奖纪录
     *
     * @param cid
     * @param rewardMoney
     * @throws ActivityException
     */
    private Long addUserBalanceRecord(Long cid, Long rewardMoney, Long pageId) {
        BalanceRecordAdd balanceRecordAdd = new BalanceRecordAdd();
        balanceRecordAdd.setUserId(cid);
        balanceRecordAdd.setAmount(rewardMoney);
        balanceRecordAdd.setBalanceType(BalanceType.CASH);
        balanceRecordAdd.setRecordType(BalanceRecordType.SURPRISE);
        balanceRecordAdd.setPageId(pageId);
        balanceRecordAdd.setAppId(RequestLocal.get().getAppId());
        balanceRecordAdd.setSlotId(Long.valueOf(RequestLocal.get().getSlotId()));
        if (StringUtils.isEmpty(userBalanceService.addAmount(balanceRecordAdd))) {
            return 0l;
        }

        //保存订单队列 给中间公示
        ConsumerDto consumerDto = consumerService.getConsumerById(cid);
        BalanceRecordVO vo = new BalanceRecordVO();
        if (null != consumerDto) {
            vo.setPhone(consumerDto.getPhone());
        } else {
            vo.setPhone(PhoneTool.createPartialNumber());
        }
        vo.setAmount(rewardMoney);
        balanceRecordService.putInRecentlyList(vo, 30);
        return rewardMoney;
    }

    @Override
    public List<String> assemblyReward(HttpServletRequest request) {

        List<String> rsp = new ArrayList<>(30);
        List<BalanceRecordVO> balanceRecordVOS = balanceRecordService.recentlyList(20);
        int size = balanceRecordVOS.size();
        if (size < 20) {
            for (int i = 0; i < (20 - size); i++) {
                BalanceRecordVO vo = new BalanceRecordVO();
                vo.setAmount(RandomUtils.nextLong(1, 51));
                vo.setPhone(PhoneTool.createPartialNumber());
                balanceRecordVOS.add(vo);
            }
        }
        rsp.addAll(WinPublicTool.getTemplateList(Constants.Template.TEMP1, balanceRecordVOS, e -> PhoneTool.getSuffixPhone(e.getPhone()), e -> Double.valueOf(e.getAmount()) / 100));
        rsp.add(WinPublicTool.buildWinString(Constants.Template.TEMP5, PhoneTool.createSuffixNumber()));
        rsp.add(WinPublicTool.buildWinString(Constants.Template.TEMP25, PhoneTool.createSuffixNumber(), template[RandomUtils.nextInt(0, 4)]));
        rsp.add(WinPublicTool.buildWinString(Constants.Template.TEMP25, PhoneTool.createSuffixNumber(), template[RandomUtils.nextInt(0, 4)]));
        rsp.add(WinPublicTool.buildWinString(Constants.Template.TEMP25, PhoneTool.createSuffixNumber(), template[RandomUtils.nextInt(0, 4)]));
        for (int i = 0; i < 6; i++) {
            rsp.add(WinPublicTool.buildWinString(Constants.Template.TEMP70, PhoneTool.createSuffixNumber(), String.valueOf(Double.valueOf(RandomUtils.nextInt(1, 101)) / 100)));
        }
        Collections.shuffle(rsp);
        return rsp;
    }

    @Override
    public Result<ActivityWinOrderDto> cashWithdraw(String alipayName, String alipayAccount, Long amount, Long pageId) {
        Long userId = RequestLocal.get().getCid();
        if (userId == null) {
            return ResultUtil.fail(ErrorCode.E0200003);
        }
        Long balanceAmount = userBalanceService.getAmount(userId, BalanceType.CASH.getType());
        if (balanceAmount - amount <= 0) {
            return ResultUtil.fail(ErrorCode.E0200017);
        }

        // 检查登录状态
        DubboResult<RspSlotBackendDetailDto> slotResult = remoteSlotBackendService.getDetail(Long.valueOf(RequestLocal.get().getSlotId()));
        if (!slotResult.isSuccess()) {
            return ResultUtil.fail(slotResult.getReturnCode(), slotResult.getMsg());
        }

        RspSlotBackendDetailDto slotBackendDetailDto = slotResult.getResult();
        if (Objects.equals(slotBackendDetailDto.getPhoneStatus(), 1) &&
                !RequestLocal.get().getLoginStatus()) {
            return ResultUtil.fail(ErrorCode.E0200003);
        }
        String config = systemConfigService.getSystemConfig("tuia_guide_page_money_withdraw_list");
        List<Config> configList = JsonUtils.jsonToList(config, Config.class);
        if (configList.stream().noneMatch(e -> e.getAmount().equals(amount))) {
            return ResultUtil.fail(ErrorCode.E0000001);
        }
        return getActivityWinOrderDtoResult(alipayName, alipayAccount, amount, pageId);
    }

    @Override
    public String creditConsume(HttpServletRequest request) {
        //兑吧扣积分
        CreditTool tool = new CreditTool(Constants.TUIA_IN_DUIBA_APP_KEY, Constants.TUIA_IN_DUIBA_APP_SECRET);
        CreditConsumeParams params;
        CreditConsumeResult result;
        try {
            params = tool.parseCreditConsume(request);
        } catch (Exception e) {
            result = new CreditConsumeResult(false);
            result.setErrorMessage(e.getMessage());
            return result.toString();
        }
        long userId = Long.parseLong(params.getUid());
        String transfer = request.getParameter("transfer");

        //2.更新账户余额及流水
        UserBalanceDto balanceDto = userBalanceService.getByType(userId, BalanceType.GAME);
        if (null == balanceDto) {
            result = new CreditConsumeResult(false);
            result.setErrorMessage("账户不存在");
            return result.toString();
        }

        if(StringUtils.isBlank(transfer) || transfer.split("_").length != 2){
            result = new CreditConsumeResult(false);
            result.setErrorMessage("参数错误");
            return result.toString();
        }

        String[] temp = transfer.split("_");
        BalanceRecordAdd addr = new BalanceRecordAdd();
        addr.setBalanceType(BalanceType.GAME);
        addr.setUserId(userId);
        addr.setRecordType(BalanceRecordType.CREDITS_CONSUME);
        addr.setAppId(Long.parseLong(temp[0]));
        addr.setSlotId(Long.parseLong(temp[1]));
        addr.setAmount(params.getCredits());
        JSONObject extInfo = new JSONObject(1);
        extInfo.put("orderNum" ,params.getOrderNum());
        addr.setExtInfo(extInfo.toJSONString());
        String orderId = userBalanceService.addAmount(addr);
        UserBalanceDto balanceNew = userBalanceService.getByType(userId, BalanceType.GAME);
        if (StringUtils.isEmpty(orderId) || balanceDto.getAmount().equals(balanceNew.getAmount())) {
            //没更新成功
            result = new CreditConsumeResult(false);
            result.setErrorMessage("系统异常,请重试");
        } else {
            result = new CreditConsumeResult(true);
            result.setBizId(orderId);
        }
        result.setCredits(balanceNew.getAmount());
        //3.构造响应
        return result.toString();
    }

    @Override
    public String creditNotify(HttpServletRequest request) {
        CreditTool tool = new CreditTool(Constants.TUIA_IN_DUIBA_APP_KEY, Constants.TUIA_IN_DUIBA_APP_SECRET);
        CreditNotifyParams params;
        try {
            params = tool.parseCreditNotify(request);
        } catch (Exception e) {
            return e.getMessage();
        }
        String transfer = request.getParameter("transfer");
        String orderNum = params.getOrderNum();
        String orderId = params.getBizId();
        if(params.isSuccess()){
            return "ok";
        }
        String[] split = transfer.split("_");
        if(StringUtils.isBlank(transfer) || split.length != 2){
            return "参数错误";
        }
        Long userId = Long.parseLong(request.getParameter("uid"));
        BalanceRecordDto creditsConsumeRecord = balanceRecordService.getCreditsConsumeRecord(userId, orderId, orderNum);
        //2.兑换失败时回滚该订单
        if(null == creditsConsumeRecord){
            return "订单不存在";
        }
        //3.查询符合兑吧订单号的订单
        BalanceRecordAdd addr = new BalanceRecordAdd();
        addr.setUserId(userId);
        addr.setSlotId(Long.parseLong(split[0]));
        addr.setAppId(Long.parseLong(split[1]));
        addr.setAmount(creditsConsumeRecord.getAmount());
        addr.setRecordType(BalanceRecordType.CREDITS_ROLLBACK);
        addr.setBalanceType(BalanceType.GAME);
        JSONObject extInfo = new JSONObject(2);
        extInfo.put("orderNum" ,orderNum);
        extInfo.put("errorMessage" ,params.getErrorMessage());
        addr.setExtInfo(extInfo.toJSONString());
        userBalanceService.addAmount(addr);

        return "ok";
    }

    private Result<ActivityWinOrderDto> getActivityWinOrderDtoResult(String alipayName, String alipayAccout, Long amount, Long pageId) {
        BalanceWithdrawReq req = new BalanceWithdrawReq();
        req.setAlipayName(alipayName);
        req.setAlipayAccount(alipayAccout);
        req.setAmount(amount);
        req.setUserId(RequestLocal.get().getCid());
        req.setDeviceId(RequestLocal.get().getDeviceId());
        req.setAppId(RequestLocal.get().getAppId());
        req.setSlotId(Long.valueOf(RequestLocal.get().getSlotId()));
        req.setPageId(pageId);
        req.setPageType(PageType.DIRECT.getCode());
        try {
            return ResultUtil.success(userBalanceService.cashWithdraw(req));
        } catch (ActivityCenterException | ActivityCenterRuntimeException e) {
            return ResultUtil.fail(e.getCode(), e.getMessage());
        }
    }

    private static class Config implements Serializable {
        private Long amount;
        private Long fee;

        public Long getAmount() {
            return amount;
        }

        public void setAmount(Long amount) {
            this.amount = amount;
        }

        public Long getFee() {
            return fee;
        }

        public void setFee(Long fee) {
            this.fee = fee;
        }
    }

}
