package org.tio.sitexxx.service.service.pay;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.redisson.api.RBucket;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.http.common.HttpRequest;
import org.tio.jfinal.kit.Kv;
import org.tio.jfinal.kit.Ret;
import org.tio.jfinal.plugin.activerecord.Db;
import org.tio.jfinal.plugin.activerecord.Page;
import org.tio.jfinal.plugin.activerecord.Record;
import org.tio.jfinal.plugin.activerecord.SqlPara;
import org.tio.sitexxx.im.server.vo.*;
import org.tio.sitexxx.service.init.RedisInit;
import org.tio.sitexxx.service.model.main.*;
import org.tio.sitexxx.service.service.ConfService;
import org.tio.sitexxx.service.service.WxWalletCoinService;
import org.tio.sitexxx.service.service.atom.AbsTxAtom;
import org.tio.sitexxx.service.service.base.UserService;
import org.tio.sitexxx.service.service.pay.base.*;
import org.tio.sitexxx.service.service.pay.impl.PayStdBizApi;
import org.tio.sitexxx.service.service.pay.impl.ncount.PayNCountApi;
import org.tio.sitexxx.service.service.pay.impl.ncount.PayNCountCallbackApi;
import org.tio.sitexxx.service.service.pay.utils.AmountUtil;
import org.tio.sitexxx.service.service.pay.utils.ncount.NCountVoUtil;
import org.tio.sitexxx.service.service.pay.utils.ncount.NRequestUtils;
import org.tio.sitexxx.service.service.pay.vo.ncount.base.NCountBaseVo;
import org.tio.sitexxx.service.utils.PeriodUtils;
import org.tio.sitexxx.service.utils.RetUtils;
import org.tio.sitexxx.service.vo.Const;
import org.tio.sitexxx.service.vo.PayConst;
import org.tio.utils.http.HttpUtils;
import org.tio.utils.json.Json;
import org.tio.utils.lock.LockUtils;

import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * <pre>
 *    @author : vagrancy
 *    e-mail : 18050829067@163.com
 *    time   : 2025-11-24 15:49
 *    desc   :
 * </pre>
 */
public class PayService implements BasePayService {
    private static Logger log = LoggerFactory.getLogger(PayService.class);

    public static BasePay<BasePayReq, BasePayResp> basePay;

    public static BaseCallbackPay<BasePayResp> baseCallbackPay;

    public static BaseBizPay bizPay;

    public static void initPay() {
        basePay = new PayNCountApi();
        baseCallbackPay = new PayNCountCallbackApi();
        bizPay = new PayStdBizApi(basePay);
    }

    public PayService() {
        initPay();
    }

    /**
     * 绑卡确定
     */
    @Override
    public Ret bindCardConfirm(BindCardConfirmVo cardVo, HttpRequest request) {
        WxWalletBankCards bankCards = WxWalletBankCards.dao.findById(cardVo.getBankcardid());
        if (bankCards == null) {
            return RetUtils.failMsg("初始化银行卡不存在");
        }
        User user = UserService.ME.getById(cardVo.getUid());
        Ret checkRet = User.checkUser(user);
        if (checkRet.isFail()) {
            return checkRet;
        }
        WxWalletBankCards update = new WxWalletBankCards();
        update.setId(bankCards.getId());
        update.setBankcode("0000");
        update.setCardtype((short)1);
        update.setStatus(Const.YesOrNo.YES);
        boolean atom = update.update();
        if (!atom) {
            return RetUtils.failMsg("保存失败");
        }
        WxWalletBankCards lastCard = WxWalletBankCards.dao.findById(bankCards.getId());
        return RetUtils.okData(lastCard);
    }

    /**
     * 绑卡
     */
    @Override
    public Ret bindCard(BindCardVo cardVo, HttpRequest request) {
        BasePayResp resp = new BasePayResp();
        resp.setOk(true);
        return bindBankCard(resp, cardVo);
    }

    /**
     * 客户端token
     */
    @Override
    public Ret getClientToken(ClientTokenVo tokenVo, HttpRequest request) {
        return RetUtils.failMsg("标准版不支持客户端拉起商户api");
    }

    /**
     * 支付列表信息
     */
    @Override
    public Ret payListInfo(Integer uid) {
        List<Record> banklist = bankcardBeanList(uid);
        Ret ret = getWalletInfo(uid);
        WxWalletCoin walletInfo = RetUtils.getOkTData(ret);
        Map<String, Object> infoRet = new HashMap<>();
        infoRet.put("banklist", banklist);
        infoRet.put("walletinfo", walletInfo);
        return RetUtils.okData(infoRet);
    }

    /**
     * 退出红包
     */
    @Override
    public Ret quickRedPacket(SendRedpacketVo redpacketVo, HttpRequest request) {
        WxWalletSendRedPacket sendRed = getRedPacketLock(redpacketVo.getRid(), false);
        if (sendRed == null) {
            return RetUtils.failMsg("操作已超时，请重试");
        }
        if (!Objects.equals(sendRed.getStatus(), PayConst.RedPacketStatus.INIT)) {
            return RetUtils.failMsg("红包状态异常");
        }
        redpacketVo.setSendRed(sendRed);
        RechargeVo rechargeVo = new RechargeVo();
        rechargeVo.setUid(redpacketVo.getUid());
        rechargeVo.setAgrno(redpacketVo.getAgrno());
        rechargeVo.setAmount(sendRed.getCny() + "");
        rechargeVo.setRemark(sendRed.getBless());
        short timeout = ConfService.getShort(Const.ConfMapping.WX_WALLET_REDPACKET_PAY_TIMEOUT, (short) 5);
        rechargeVo.setTimeout(timeout);
        String notifyUrl = PayConst.CallBackUrl.REDPACKET + rechargeVo.getUid();
        rechargeVo.setNotifyUrl(notifyUrl);
        BasePayReq req = new BasePayReq(request);
        req.setParams(rechargeVo.toMap());
        BasePayResp resp = rechargeTwo(req, rechargeVo.getUid());
        if (!resp.isBizOk()) {
            return RetUtils.failMsg(resp.getMerMsg());
        }
        redpacketVo.setNotifyUrl(notifyUrl);
        redpacketVo.setPaytimeout(timeout);
        return quickRedPacketTwo(resp, redpacketVo);
    }

    @Override
    public Ret withholdList(Integer uid, Integer pageNumber) {
        if (pageNumber == null || pageNumber <= 0) {
            pageNumber = 1;
        }
        Kv params = Kv.by("uid", uid).set("status", PayConst.WalletChangeStatus.SUCCESS);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("withhold.list", params);
        Page<Record> withholdPage = Db.use(Const.Db.TIO_SITE_MAIN).paginate(pageNumber, 16, sqlPara);
        return RetUtils.okPage(withholdPage);
    }

    @Override
    public Ret sendRedPacket(SendRedpacketVo redpacketVo, HttpRequest request) {
        return RetUtils.failMsg("标准版不支持商户发红包逻辑");
    }

    /**
     * 提现查询
     */
    @Override
    public Ret withholdQuery(WithholdQueryVo withholdQueryVo, HttpRequest request) {
        if (withholdQueryVo == null || StrUtil.isBlank(withholdQueryVo.getReqid())) {
            return RetUtils.failMsg("参数异常");
        }
        WxWalletWithholdItems item = PayStdBizApi.getWithholdInfo(withholdQueryVo.getWid());
        if (item == null) {
            return RetUtils.failMsg("提现订单不存在");
        }
        if (Objects.equals(item.getStatus(), PayConst.WalletChangeStatus.SUCCESS) || Objects.equals(item.getStatus(), PayConst.WalletChangeStatus.FAIL)) {
            return RetUtils.okData(item);
        }
        BasePayReq req = new BasePayReq(request);
        req.setParams(withholdQueryVo.toMap());
        BasePayResp resp = basePay.withholdQuery(req, withholdQueryVo.getUid());
        return bizPay.withholdQuery(request, resp, withholdQueryVo, true);
    }

    public BasePayResp rechargeTwo(BasePayReq payQuest, Integer uid) {
        String tranCode = PayConst.TranCode.RECHARGE;
        NCountBaseVo baseVo = NCountVoUtil.getBaseVoByTranCode(tranCode, payQuest.getParams(), uid);
        Ret ret = NRequestUtils.nRequest(baseVo);
        BasePayResp resp = NRequestUtils.checkRet(ret);
        if (resp.isOk()) {
            resp.setResp(rechargeThree(resp.getResp(), uid));
        }
        return resp;
    }

    public Map<String, Object> rechargeThree(Map<String, Object> resp, Integer uid) {
        Map<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("merid", "300008795977");
        result.put("reqid", MapUtil.getStr(resp, "merOrderId"));
        result.put("merorderid", MapUtil.getStr(resp, "ncountOrderId"));
        String bizcreattime = MapUtil.getStr(resp, "submitTime");
        if (StrUtil.isNotBlank(bizcreattime)) {
            result.put("bizcreattime", DateUtil.parse(bizcreattime, "yyyyMMddHHmmss"));
        }
        result.put("ordererrormsg", errMsg(resp));
        NRequestUtils.respFormat(resp, result);
        return result;
    }

    public Ret quickRedPacketTwo(BasePayResp resp, SendRedpacketVo redpacketVo) {
        if (!resp.isBizOk()) {
            return RetUtils.failMsg(resp.getMerMsg());
        }
        short timeout = redpacketVo.getPaytimeout();
        WxWalletSendRedPacket redPacket = WxWalletSendRedPacket.mapToBean(resp.getResp());
        redPacket.setAgrno(redpacketVo.getAgrno());
        redPacket.setPaynotifyurl(redpacketVo.getNotifyUrl());
        redPacket.setPaytimeout(timeout);
        redPacket.setStatus(PayConst.RedPacketStatus.PAYING);
        redPacket.setId(redpacketVo.getRid());
        redPacket.setPaytype(PayConst.RedPayType.BANKCARD);
        redPacket.setBacktime(DateUtil.offsetMinute(new DateTime(), timeout));
        Ret ret = updateRedPacketLock(redPacket, PayConst.RedPacketStatus.INIT, true);
        if (ret.isFail()) {
            return ret;
        }
        return RetUtils.okData(redPacket);
    }

    /**
     * 提现
     */
    @Override
    public Ret withhold(WithholdVo withholdVo, HttpRequest request) {
        if (withholdVo == null || withholdVo.getUid() == null) {
            return RetUtils.failMsg("参数异常");
        }
        if (StrUtil.isBlank(withholdVo.getPaypwd())) {
            return RetUtils.failMsg("支付密码为空");
        }
        WxWalletWithholdItems holditems =new WxWalletWithholdItems();
        holditems.setAmount(Long.parseLong(withholdVo.getAmount()));
        holditems.setAgrno(withholdVo.getAgrno());
        holditems.setNotifyurl(withholdVo.getNotifyUrl());
        holditems.setArrivalamount(holditems.getAmount());
        holditems.setMerstatus("-1");
        holditems.setStatus(PayConst.WalletChangeStatus.LOCAL);
        holditems.setResptime(new DateTime());
        holditems.setRemark(withholdVo.getRemark());
        holditems.setUid(withholdVo.getUid());
        String appName = ConfService.getString(Const.APP_NAME, "");
        if(appName.equals("联诚")||appName.equals("畅联")) {
            RedissonClient redissonClient = RedisInit.get();
            RBucket<Object> rBucket = redissonClient.getBucket("usdt_rate");
            Object object = rBucket.get();
            Double rate = Double.valueOf(object.toString());
            long cny = Long.parseLong(withholdVo.getAmount());
            Double resultCNY = rate*cny;
            holditems.setRmb(resultCNY.intValue());
        }
        holditems.save();
        Integer uid = withholdVo.getUid();
        WxWalletCoinService.me.subCoin(uid, holditems.getAmount());

        WxWalletCoinItem coinItem = new WxWalletCoinItem();
        coinItem.setUid(uid);
        coinItem.setMode(PayConst.WalletMode.WIHTHOLD);
        coinItem.setCoinflag( Const.CoinFlag.PAY);
        coinItem.setStatus(PayConst.WalletChangeStatus.SUCCESS);
        coinItem.setCny(holditems.getAmount());
        coinItem.setOrderstatus("SUCCESS");
        coinItem.setBizid(1);
        coinItem.setReqid(System.currentTimeMillis()+"");
        coinItem.setBizcompletetime(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
        coinItem.setBizcreattime(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
        coinItem.setRemark("提现");
        coinItem.save();
        return Ret.ok();
    }


    /**
     * 初始化银行卡
     */
    public Ret bindBankCard(BasePayResp resp, BindCardVo cardVo) {
        if (!resp.isBizOk()) {
            return RetUtils.failMsg(resp.getMerMsg());
        }
        User user = UserService.ME.getById(cardVo.getUid());
        Ret checkRet = User.checkUser(user);
        if (checkRet.isFail()) {
            return checkRet;
        }
        WxWalletBankCards bankCards =new WxWalletBankCards();;
        bankCards.setUid(cardVo.getUid());
        bankCards.setPhone(cardVo.getMobile());
        bankCards.setUsername(cardVo.getName());
        bankCards.setStatus(Const.Status.DISABLED);
        bankCards.setCardno(cardVo.getBankcardno());
        bankCards.setAgrno(cardVo.getBankcardno());
        String bankname = cardVo.getBankname();
        if(bankname != null) {
            bankCards.setBankname(cardVo.getBankname());
        }else {
            try {
                Response response = HttpUtils.get("https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&cardNo="+bankCards.getCardno()+"&cardBinCheck=true");
                ResponseBody body = response.body();
                String string = body.toString();
                if(string != null) {
                    JSONObject parseObject = JSON.parseObject(string);
                    String bankCode = parseObject.getString("bank");
                    WxWalletBankInfo bankInfo = WxWalletBankInfo.dao.findFirst("select  bankname from  wx_wallet_bank_info where bankcode = ?",bankCode);
                    if(bankInfo != null) {
                        String bankname2 = bankInfo.getBankname();
                        bankCards.setBankname(bankname2);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        bankCards.replaceSave();
        return RetUtils.okData(bankCards);
    }

    /**
     * 实名信息
     */
    @Override
    public Ret realInfo(Integer uid) {
        Record record = Db.use(Const.Db.TIO_SITE_MAIN)
                .findFirst("select id,mobile,`name`," + "CONCAT(SUBSTR(cardno,1,6),'********',SUBSTR(cardno,15)) cardno," + "`status` " + "from wx_wallet_info where uid = ?", uid);
        return RetUtils.okData(record);
    }

    /**
     * 红包信息
     */
    @Override
    public Ret redInfo(Integer rid) {
        Kv params = Kv.by("rid", rid);
        RedissonClient redissonClient = RedisInit.get();
        RBucket<Object> bucket = redissonClient.getBucket("redInfo_"+rid);
        //改为缓存里拿取红包信息
        WxWalletSendRedPacket redPacket =(WxWalletSendRedPacket)bucket.get();
        if (redPacket == null || Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.CANCEL) || Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.FAIL)) {
            return RetUtils.failMsg("红包不存在");
        }
        RList<WxWalletGrabRedItem> list =redissonClient.getList("grabRedItem_"+redPacket.getId());
        Map<String, Object> infoRet = new HashMap<>();
        infoRet.put("info", redPacket);
        if(list == null ||list.size()==0) {
            SqlPara sqlPara2 = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("redpacket.redinfoGrablist", params);
            List<WxWalletGrabRedItem> grabRedItem = WxWalletGrabRedItem.dao.find(sqlPara2);
            if (CollectionUtil.isNotEmpty(grabRedItem)) {
                infoRet.put("grablist", grabRedItem);
            } else {
                infoRet.put("grablist", new ArrayList<WxWalletGrabRedItem>());
            }
        }else {
            String json = JSON.toJSONString(list);
            List<WxWalletGrabRedItem> array = JSON.parseArray(json,WxWalletGrabRedItem.class);
            infoRet.put("grablist", array);
        }
        return RetUtils.okData(infoRet);
    }
    /**
     * @param uid
     * @param pageNumber
     * @param mode
     * @return
     * @author xufei 2020年11月26日 上午10:43:38
     */
    @Override
    public Ret getWalletItems(Integer uid, Integer pageNumber, Short mode) {
        if (pageNumber == null || pageNumber <= 0) {
            pageNumber = 1;
        }
        Kv params = Kv.by("uid", uid);
        if (mode != null) {
            params.set("mode", mode);
        }
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("wallet.items", params);
        Page<Record> rechargePage = Db.use(Const.Db.TIO_SITE_MAIN).paginate(pageNumber, 16, sqlPara);
        return RetUtils.okPage(rechargePage);
    }

    /**
     * 红包状态
     * @param user
     * @param rid 红包序号
     * @return
     */
    @Override
    public Ret redStatus(User user, Integer rid) {
        Map<String, Object> statusRet = new HashMap<>();
        RedissonClient redissonClient = RedisInit.get();
        RBucket<Object> bucket = redissonClient.getBucket("redInfo_"+rid);
        //改为缓存里拿取红包信息
        WxWalletSendRedPacket redPacket =(WxWalletSendRedPacket)bucket.get();
        if (redPacket == null || Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.CANCEL) || Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.FAIL)) {
            return RetUtils.failMsg("红包不存在");
        }
        RList<Object> list = redissonClient.getList("grabRedItem_"+redPacket.getId());
        if(list!=null && list.size()>0) {
            Optional<Object> findFirst = list.stream().filter(item -> {
                WxWalletGrabRedItem  grabRed = (WxWalletGrabRedItem)item;
                return	grabRed.getUid().equals(user.getId());
            }).findFirst();

            if(findFirst.isPresent()) {
                WxWalletGrabRedItem grabRedItem =(WxWalletGrabRedItem)findFirst.get();
                statusRet.put("grabcny", grabRedItem.getCny());
                statusRet.put("grabstatus", grabRedItem.getStatus());
            }else {
                statusRet.put("grabstatus", PayConst.RedRandomStatus.INIT);
            }
        }else {
            statusRet.put("grabstatus", PayConst.RedRandomStatus.INIT);
        }
        statusRet.put("redstatus", redPacket.getStatus());
        return RetUtils.okData(statusRet);
    }

    /**
     * @param uid
     * @param period
     * @return
     */
    @Override
    public Ret sendRedPacketStat(Integer uid, String period) {
        Kv params = Kv.by("uid", uid).set("statuses",
                "'" + PayConst.RedPacketStatus.SUCCESS + "','" + PayConst.RedPacketStatus.PROCESS + "','" + PayConst.RedPacketStatus.TIMEOUT + "'");
        DateTime dateTime = new DateTime();
        if (StrUtil.isNotBlank(period)) {
            dateTime = PeriodUtils.getDateByPeriod(period);
        }
        String starttime = DateUtil.format(DateUtil.beginOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        String endtime = DateUtil.format(DateUtil.endOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        params.set("starttime", starttime).set("endtime", endtime);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("redpacket.sendstat", params);
        Record redpacketstat = Db.use(Const.Db.TIO_SITE_MAIN).findFirst(sqlPara);
        return RetUtils.okData(redpacketstat);
    }

    /**
     *
     * @param uid
     * @param period
     * @return
     * @author xufei 2020年11月27日 下午4:52:51
     */
    @Override
    public Ret grabRedPacketStat(Integer uid, String period) {
        Kv params = Kv.by("uid", uid).set("status", PayConst.RedRandomStatus.SUCCESS);
        DateTime dateTime = new DateTime();
        if (StrUtil.isNotBlank(period)) {
            dateTime = PeriodUtils.getDateByPeriod(period);
        }
        String starttime = DateUtil.format(DateUtil.beginOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        String endtime = DateUtil.format(DateUtil.endOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        params.set("starttime", starttime).set("endtime", endtime);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("redpacket.grabstat", params);
        Record redpacketstat = Db.use(Const.Db.TIO_SITE_MAIN).findFirst(sqlPara);
        return RetUtils.okData(redpacketstat);
    }

    /**
     * 银行卡列表
     *
     * @param uid
     * @return
     * @author xufei 2021年3月12日 上午11:13:29
     */
    @Override
    public Ret bankcardList(Integer uid) {
        List<Record> banklist = bankcardBeanList(uid);
        return RetUtils.okList(banklist);
    }

    /**
     * @param uid
     * @return
     * @author xufei 2021年3月17日 下午4:06:11
     */
    private List<Record> bankcardBeanList(Integer uid) {
        Kv params = Kv.by("uid", uid).set("status", Const.Status.NORMAL);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("wallet.banklist", params);
        return Db.use(Const.Db.TIO_SITE_MAIN).find(sqlPara);
    }

    /**
     * 抢红包记录
     *
     * @param uid
     * @param pageNumber
     * @return
     * @author xufei 2020年11月19日 上午11:33:07
     */
    @Override
    public Ret grabRedPacketList(Integer uid, Integer pageNumber, String period) {
        Kv params = Kv.by("uid", uid).set("status", PayConst.RedRandomStatus.SUCCESS);
        DateTime dateTime = new DateTime();
        if (StrUtil.isNotBlank(period)) {
            dateTime = PeriodUtils.getDateByPeriod(period);
        }
        String starttime = DateUtil.format(DateUtil.beginOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        String endtime = DateUtil.format(DateUtil.endOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        params.set("starttime", starttime).set("endtime", endtime);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("redpacket.grablist", params);
        Page<Record> redpacketPage = Db.use(Const.Db.TIO_SITE_MAIN).paginate(pageNumber, 16, sqlPara);
        return RetUtils.okPage(redpacketPage);
    }

    /**
     * 发送红包记录
     *
     * @param uid
     * @param pageNumber
     * @return 13805730416 郭 13905817500 张
     * @author xufei 2020年11月19日 上午11:32:00
     */
    @Override
    public Ret sendRedPacketList(Integer uid, Integer pageNumber, String period) {
        Kv params = Kv.by("uid", uid).set("statuses",
                "'" + PayConst.RedPacketStatus.SUCCESS + "','" + PayConst.RedPacketStatus.PROCESS + "','" + PayConst.RedPacketStatus.TIMEOUT + "'");
        DateTime dateTime = new DateTime();
        if (StrUtil.isNotBlank(period)) {
            dateTime = PeriodUtils.getDateByPeriod(period);
        }
        String starttime = DateUtil.format(DateUtil.beginOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        String endtime = DateUtil.format(DateUtil.endOfYear(dateTime), DatePattern.NORM_DATETIME_PATTERN);
        params.set("starttime", starttime).set("endtime", endtime);
        SqlPara sqlPara = Db.use(Const.Db.TIO_SITE_MAIN).getSqlPara("redpacket.sendlist", params);
        Page<Record> redpacketPage = Db.use(Const.Db.TIO_SITE_MAIN).paginate(pageNumber, 16, sqlPara);
        return RetUtils.okPage(redpacketPage);
    }

    /**
     * 钱包信息
     * @return
     */
    @Override
    public Ret getWalletInfo(Integer uid) {
        WxWalletCoin coin = WxWalletCoin.dao.findFirst("select id,cny from wx_wallet_coin where uid = ?", uid);
        if (coin == null) {
            return RetUtils.failMsg("钱包不存在");
        }
        return RetUtils.okData(coin);
    }
    /**
     * 解除绑定银行卡
     */
    @Override
    public Ret unBindCard(Integer uid, String bankCardId) {
        WxWalletBankCards removeCard = WxWalletBankCards.dao.findById(bankCardId);
        if (removeCard == null) {
            return RetUtils.failMsg("银行卡不存在");
        }
        if (Objects.equals(Const.Status.DELETE, removeCard.getStatus())) {
            return RetUtils.failMsg("银行卡已移除");
        }
        if (!Objects.equals(uid, removeCard.getUid())) {
            return RetUtils.grantError();
        }
        WxWalletBankCards update = new WxWalletBankCards();
        update.setId(removeCard.getId());
        update.setStatus(Const.Status.DELETE);
        boolean atom = update.update();
        if (!atom) {
            return RetUtils.failMsg("移除银行卡前置失败");
        }
        return removeBankCardTwo(removeCard);
    }

    /*
     * 移除银行卡：
     * 1、卡必现存在
     * 2、卡用户一致
     * 3、注意:未进行用户密码验证
     */
    public Ret removeBankCardTwo(WxWalletBankCards bean) {
        WxWalletBankCards update = new WxWalletBankCards();
        update.setId(bean.getId());
        update.setDeltime(new Date());
        update.setDelid(bean.getId());
        boolean atom = update.update();
        if (!atom) {
            return RetUtils.failMsg("移除银行卡信息保存失败");
        }
        return RetUtils.okData(bean);
    }

    /**
     * @param redpacketVo
     * @return
     */
    @Override
    public Ret initRedPacket(SendRedpacketVo redpacketVo) {
        if (redpacketVo.getNum() == null || redpacketVo.getMode() == null) {
            return RetUtils.invalidParam();
        }
        short timeout = ConfService.getShort(Const.ConfMapping.WX_WALLET_REDPACKET_INT_TIMEOUT, (short) 20);
        Integer uid = redpacketVo.getUid();
        Short mode = redpacketVo.getMode();
        Short totalNum = redpacketVo.getNum();
        Short chatmode = redpacketVo.getChatmode();
        AbsTxAtom atom = new AbsTxAtom() {

            @Override
            public boolean noTxRun() {
                //初始化红包主表
                WxWalletSendRedPacket redPacket = new WxWalletSendRedPacket();
                DateTime createtime = new DateTime();
                redPacket.setChatmode(redPacket.getChatmode());
                redPacket.setChatbizid(redpacketVo.getBizid());
                redPacket.setUid(uid);
                redPacket.setMode(mode);
                redPacket.setChatmode(redpacketVo.getChatmode());
                redPacket.setBless(redpacketVo.getRemark());
                redPacket.setCny(Long.parseLong(redpacketVo.getCny()));
                redPacket.setNum(totalNum);
                redPacket.setStatus(PayConst.RedPacketStatus.INIT);
                redPacket.setBacktime(DateUtil.offsetMinute(createtime, timeout));
                redPacket.setCreatetime(createtime);
                redPacket.setUpdatetime(createtime);
                if (Objects.equals(Const.ChatMode.P2P, redpacketVo.getChatmode())) {
                    redPacket.setAcceptuid(redpacketVo.getBizid().intValue());
                }
                boolean init = redPacket.save();
                if (!init) {
                    return failRet("红包初始化异常");
                }
                Integer rid = redPacket.getId();
                Long totalCny = redPacket.getCny();
                if (Objects.equals(totalNum, (short) 1)) {
                    Integer senduid = null;
                    if (Objects.equals(chatmode, Const.ChatMode.P2P)) {
                        senduid = redpacketVo.getBizid().intValue();
                    }
                    boolean childinit = initRedRandom(rid, senduid, totalCny, mode, (short) 1);
                    if (!childinit) {
                        return failRet("子红包初始化异常");
                    }
                } else {
                    if (Objects.equals(PayConst.RedPackMode.NORMAL, mode)) {
                        Long singleCny = Long.parseLong(redpacketVo.getSinglecny());
                        Long totalMount = singleCny*totalNum;
                        if(!totalMount.equals(Long.valueOf(redpacketVo.getCny()))) {
                            return failRet("子红包初始化异常");
                        }
                        for (short i = 1; i <= totalNum; i++) {
                            boolean childinit = initRedRandom(rid, null, singleCny, mode, i);
                            if (!childinit) {
                                return failRet("子红包初始化异常");
                            }
                        }
                    } else {
                        Long minRed = (long) 1;
                        Random random = new Random();
                        for (short i = 1; i < totalNum; i++) {
                            Long safe = (long) Math.ceil(((double) (totalCny - (totalNum - i) * minRed)) / (totalNum - i));
                            int randnum = (int) (safe - minRed);
                            Long cny = minRed;
                            if (randnum > 0) {
                                cny = random.nextInt(randnum) + minRed;
                            }
                            totalCny = totalCny - cny;
                            if(cny<0) {
                                return failRet("子红包初始化异常");
                            }
                            boolean childinit = initRedRandom(rid, null, cny, mode, i);
                            if (!childinit) {
                                return failRet("子红包初始化异常");
                            }
                        }
                        boolean childinit = initRedRandom(rid, null, totalCny, mode, totalNum);
                        if (!childinit) {
                            return failRet("子红包初始化异常");
                        }
                    }
                }
                return okRet(rid);
            }
        };
        Db.use(Const.Db.TIO_SITE_MAIN).tx(atom);
        return atom.getRetObj();
    }

    /**
     * @return
     * @throws Exception
     * @author xufei
     * 2021年4月9日 上午10:46:03
     */
    @Override
    public Ret redpacketJob() throws Exception {
        try {
            dealInitOrder();
            dealPayOrder();
            dealPayConfirmOrder();
            dealTimeoutOrder();
            dealRedPacketOutTime();
            //getRate();
        } catch (Exception e) {
            throw e;
        }
        return RetUtils.okOper();
    }

    /**
     * 超时处理
     * @author xufei
     * 2021年3月24日 下午7:25:59
     */
    private void dealTimeoutOrder() {
        List<WxWalletSendRedPacket> timeoutRedpackets = WxWalletSendRedPacket.dao.find("select * from wx_wallet_send_red_packet where `status` = ? and backtime < ?",
                PayConst.RedPacketStatus.PROCESS, new DateTime());
        if (CollectionUtil.isNotEmpty(timeoutRedpackets)) {
            int dealcount = 0;
            for (WxWalletSendRedPacket item : timeoutRedpackets) {
                Ret ret = bizPay.redpacketTimeOut(item, true);
                if (ret.isFail()) {
                    log.error("超时处理异常：{}", RetUtils.getRetMsg(ret));
                    continue;
                }
                dealcount++;
            }
        }

    }


    /**
     * 红包退还
     * @author xufei
     * 2021年3月24日 下午7:25:59
     */
    private void dealRedPacketOutTime() {
        synchronized (baseCallbackPay) {
            System.out.println("开始处理超时红包");
            Date date = new Date();
            DateTime dateTime = DateUtil.offsetDay(date,-1);
            List<Record> find = Db.use(Const.Db.TIO_SITE_MAIN).find("select c.id,c.uid,c.cny,ifnull(sum(t.cny),0) reviceNum from wx_wallet_send_red_packet  c left join wx_wallet_grab_red_item t  on c.id =t.rid  where c.starttime< ?  and  c.starttime > '2022-12-08 15:00:00'  and  (c.status= ? or  c.status= ?) group by c.id",dateTime,1,6);
            //退还所有红包
            for (Record record : find) {
                Integer uid = record.getInt("uid");
                Integer id = record.getInt("id");
                Long cny = record.getLong("cny");
                Long reviceNum = record.getLong("reviceNum");
                //需要退回的金额
                Long tui = cny - reviceNum;

                WxWalletCoinItem coinItem = new WxWalletCoinItem();
                coinItem.setUid(uid);
                coinItem.setMode(PayConst.WalletMode.REDPACKET);
                coinItem.setCoinflag( Const.CoinFlag.INCOME);
                coinItem.setStatus(PayConst.WalletChangeStatus.SUCCESS);
                coinItem.setCny(tui);
                coinItem.setOrderstatus("SUCCESS");
                coinItem.setBizid(id);
                coinItem.setReqid("123");
                coinItem.setBizcompletetime(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
                coinItem.setBizcreattime(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
                coinItem.setRemark("红包超时退回");
                coinItem.save();

                WxWalletSendRedPacket packet = new WxWalletSendRedPacket();
                packet.setId(id);
                packet.setEndtime(new Date());
                packet.setStatus((short)5);
                packet.update();
                RedissonClient redissonClient = RedisInit.get();
                RBucket<Object> bucket = redissonClient.getBucket("redInfo_"+id);
                bucket.delete();
                WxWalletCoinService.me.addCoid(uid, tui);
            }

            System.out.println("超时红包处理结束");

        }
    }

    /**
     * 初始化红包超时处理
     * @author xufei
     * 2021年3月24日 下午5:59:05
     */
    private void dealInitOrder() {
        List<WxWalletSendRedPacket> redPackets = WxWalletSendRedPacket.dao.find("select * from wx_wallet_send_red_packet where `status`= ? and backtime < ?",
                PayConst.RedPacketStatus.INIT, new DateTime());
        if (CollectionUtil.isNotEmpty(redPackets)) {
            int dealcount = 0;
            for (WxWalletSendRedPacket item : redPackets) {
                //判断是否有reqid，进行逻辑判断:此处转账不成功，不会留下订单信息
                Ret ret = delRedPacketLock(item.getId(), item.getStatus(), true);
                if (ret.isOk()) {
                    dealcount++;
                }
            }
        }
    }

    /**
     * 快捷支付预下单红包超时处理
     * @author xufei
     * 2021年3月24日 下午5:59:27
     */
    private void dealPayOrder() {
        List<WxWalletSendRedPacket> quickPackets = WxWalletSendRedPacket.dao.find("select * from wx_wallet_send_red_packet where `status`= ? and backtime < ?",
                PayConst.RedPacketStatus.PAYING, new DateTime());
        if (CollectionUtil.isNotEmpty(quickPackets)) {
            int dealcount = 0;
            for (WxWalletSendRedPacket item : quickPackets) {
                if (StrUtil.isBlank(item.getReqid())) {
                    WxWalletSendRedPacket update = new WxWalletSendRedPacket();
                    update.setStatus(PayConst.RedPacketStatus.FAIL);
                    update.setRemark("系统发现快捷支付发起--无reqid记录");
                    update.setId(item.getId());
                    Ret ret = bizPay.updateRedPacketLock(update, item.getStatus(), true);
                    if (ret.isOk()) {
                        dealcount++;
                    }
                    log.error("出现发短信状态-但是没有reqiq的记录：red:{}", Json.toJson(item));
                    continue;
                }
                RedpacketQueryVo queryVo = new RedpacketQueryVo();
                queryVo.setUid(item.getUid());
                queryVo.setReqid(item.getReqid());
                queryVo.setRid(item.getId());
                BasePayReq req = new BasePayReq(null);
                req.setParams(queryVo.toMap());
                BasePayResp resp = basePay.redpacketQuery(req, queryVo.getUid());
                if (!resp.isOk()) {
                    log.error("查询响应错误：{}", resp.getMerMsg());
                }
                dealcount++;
                Ret ret = bizPay.redpacketJobQuery(resp, queryVo, true);
                if (ret.isFail()) {
                    log.error("查询处理错误：{}", RetUtils.getRetMsg(ret));
                }
            }
        }
    }

    /**
     * 订单确认红包超时处理
     * @author xufei
     * 2021年3月24日 下午6:09:44
     */
    private void dealPayConfirmOrder() {
        List<WxWalletSendRedPacket> confirmPackets = WxWalletSendRedPacket.dao.find("select * from wx_wallet_send_red_packet where `status`= ? and backtime < ?",
                PayConst.RedPacketStatus.PAYING_CONFIRM, new DateTime());
        if (CollectionUtil.isNotEmpty(confirmPackets)) {
            int dealcount = 0;
            for (WxWalletSendRedPacket item : confirmPackets) {
                if (StrUtil.isBlank(item.getReqid())) {
                    if (Objects.equals(item.getPaytype(), PayConst.RedPayType.BANKCARD)) {
                        WxWalletSendRedPacket update = new WxWalletSendRedPacket();
                        update.setStatus(PayConst.RedPacketStatus.FAIL);
                        update.setRemark("系统发现支付确认--无reqid记录");
                        update.setId(item.getId());
                        Ret ret = bizPay.updateRedPacketLock(update, item.getStatus(), true);
                        if (ret.isOk()) {
                            dealcount++;
                        }
                        log.error("出现支付确认状态-但是没有reqiq的记录：red:{}", Json.toJson(item));
                        continue;
                    } else {
                        Ret ret = delRedPacketLock(item.getId(), item.getStatus(), true);
                        if (ret.isOk()) {
                            dealcount++;
                        }
                        continue;
                    }

                }
                RedpacketQueryVo queryVo = new RedpacketQueryVo();
                queryVo.setUid(item.getUid());
                queryVo.setReqid(item.getReqid());
                queryVo.setRid(item.getId());
                BasePayReq req = new BasePayReq(null);
                req.setParams(queryVo.toMap());
                BasePayResp resp = basePay.redpacketQuery(req, queryVo.getUid());
                if (!resp.isOk()) {
                    log.error("查询响应错误：{}", resp.getMerMsg());
                    continue;
                }
                Ret ret = bizPay.redpacketJobQuery(resp, queryVo, true);
                if (ret.isFail()) {
                    log.error("查询处理错误：{}", RetUtils.getRetMsg(ret));
                    continue;
                }
                dealcount++;
            }
        }
    }

    /**
     * 删除红包-加锁选择
     * @param rid
     * @param lock
     * @return
     * @author xufei
     * 2021年3月23日 下午3:48:06
     */
    private Ret delRedPacketLock(Integer rid, Short status, boolean lock) {
        if (lock) {
            ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_REDPACKET_GET_LOCK + "." + rid, WxWalletSendRedPacket.class);
            ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
            writeLock.lock();
            try {
                WxWalletSendRedPacket sendRed = WxWalletSendRedPacket.dao.findById(rid);
                if (sendRed == null) {
                    log.error("删除红包时，发现红包已经不存在,rid:{},status:{}", rid, status);
                    return RetUtils.failMsg("删除-红包已被删除");
                }
                if (!Objects.equals(status, sendRed.getStatus())) {
                    log.error("删除红包时，发现红包状态发送变更,red:{},delstatus:{}", Json.toJson(sendRed), status);
                    return RetUtils.failMsg("删除-红包状态发生变更");
                }

                boolean del = WxWalletSendRedPacket.dao.deleteById(rid);
                if (!del) {
                    return RetUtils.failMsg("删除红包数据异常");
                }
                return RetUtils.okOper();
            } catch (Exception e) {
                log.error("", e);
            } finally {
                writeLock.unlock();
            }
        } else {
            boolean del = WxWalletSendRedPacket.dao.deleteById(rid);
            if (!del) {
                return RetUtils.failMsg("删除红包数据异常");
            }
        }
        return RetUtils.failMsg("系统异常");
    }

    /**
     * 获取红包时是否加锁
     * 场景：一般针对红包初始化到预支付过程中的超时处理
     * @param rid
     * @param lock
     * @return
     * @author xufei
     * 2021年3月23日 上午11:09:16
     */
    public WxWalletSendRedPacket getRedPacketLock(Integer rid, Boolean lock) {
        if (lock == null) {
            lock = false;
        }
        if (lock) {
            ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_REDPACKET_GET_LOCK + "." + rid, WxWalletSendRedPacket.class);
            ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
            writeLock.lock();
            try {
                return WxWalletSendRedPacket.dao.findById(rid);
            } catch (Exception e) {
            } finally {
                writeLock.unlock();
            }
        } else {
            return WxWalletSendRedPacket.dao.findById(rid);
        }
        return null;
    }

    /**
     * @param redpacketVo
     * @param request
     * @return
     * @author xufei 2021年3月15日 下午4:20:32
     */
    @Override
    public Ret payRedPacket(SendRedpacketVo redpacketVo, HttpRequest request, User user) {
        WxWalletSendRedPacket sendRed = getRedPacketLock(redpacketVo.getRid(), false);
        if (sendRed == null) {
            return RetUtils.failMsg("操作已超时，请重试");
        }
        BasePayResp resp = new BasePayResp();
        short timeout = ConfService.getShort(Const.ConfMapping.WX_WALLET_REDPACKET_PAY_TIMEOUT, (short) 5);
        DateTime apitime = new DateTime();
        if (Objects.equals(redpacketVo.getPaytype(), PayConst.RedPayType.BANKCARD)) {
            //银行卡支付-验证码确认-PAYING-->PAYING_CONFIRM
            if (!Objects.equals(sendRed.getStatus(), PayConst.RedPacketStatus.PAYING)) {
                return RetUtils.failMsg("请先发送短信");
            }
            WxWalletSendRedPacket update = new WxWalletSendRedPacket();
            update.setId(redpacketVo.getRid());
            update.setApitime(apitime);
            update.setPaytype(PayConst.RedPayType.BANKCARD);
            update.setBacktime(DateUtil.offsetMinute(apitime, timeout));
            update.setStatus(PayConst.RedPacketStatus.PAYING_CONFIRM);
            Ret ret = updateRedPacketLock(update, PayConst.RedPacketStatus.PAYING, true);
            if (ret.isFail()) {
                return ret;
            }
            RechargeConfirmVo rechargeVo = new RechargeConfirmVo();
            rechargeVo.setUid(redpacketVo.getUid());
            rechargeVo.setSmscode(redpacketVo.getSmscode());
            rechargeVo.setMerorderid(redpacketVo.getMerorderid());
            BasePayReq req = new BasePayReq(request);
            req.setParams(rechargeVo.toMap());
            resp = rechargeConfirm(req, rechargeVo.getUid());
            if (!resp.isBizOk()) {
                WxWalletSendRedPacket rollbackupdate = new WxWalletSendRedPacket();
                rollbackupdate.setId(redpacketVo.getRid());
                rollbackupdate.setStatus(sendRed.getStatus());
                rollbackupdate.setOrdererrormsg(resp.getMerMsg());
                rollbackupdate.setApitime(sendRed.getApitime());
                rollbackupdate.setPaytype(sendRed.getPaytype());
                rollbackupdate.setBacktime(sendRed.getBacktime());
                Ret rollback = updateRedPacketLock(rollbackupdate, PayConst.RedPacketStatus.PAYING_CONFIRM, true);
                if (rollback.isFail()) {
                    return rollback;
                }
                return RetUtils.failMsg(resp.getMerMsg());
            }
        }
        else {//密码支付-INIT-->PAYING_CONFIRM
            WxWalletSendRedPacket update = new WxWalletSendRedPacket();
            update.setId(redpacketVo.getRid());
            update.setApitime(apitime);
            update.setPaytype(PayConst.RedPayType.CNY);
            update.setBacktime(DateUtil.offsetMinute(apitime, timeout));
            update.setStatus(PayConst.RedPacketStatus.PAYING_CONFIRM);
            if (StrUtil.isNotBlank(sendRed.getReqid())) {
                Ret ret = updateRedPacketLock(update, PayConst.RedPacketStatus.PAYING, true);
                if (ret.isFail()) {
                    return ret;
                }
            } else {
                Ret ret = updateRedPacketLock(update, PayConst.RedPacketStatus.INIT, true);
                if (ret.isFail()) {
                    return ret;
                }
            }
            Map<String, Object> result = new HashMap<>();
            result.put("uid", sendRed.getUid());
            result.put("merid", sendRed.getMerid());
            result.put("reqid", sendRed.getReqid());
            result.put("merorderid", sendRed.getMerorderid());
            result.put("payacctamount", sendRed.getCny());
            result.put("recvacctamount",sendRed.getCny());
            result.put("merstatus", "SUCCESS");

            resp.setOk(true);
            resp.setResp(result);
            if (!resp.isBizOk()) {
                WxWalletSendRedPacket rollbackupdate = new WxWalletSendRedPacket();
                rollbackupdate.setId(redpacketVo.getRid());
                rollbackupdate.setStatus(sendRed.getStatus());
                rollbackupdate.setOrdererrormsg(resp.getMerMsg());
                rollbackupdate.setApitime(sendRed.getApitime());
                if (sendRed.getPaytype() != null) {
                    rollbackupdate.setPaytype(sendRed.getPaytype());
                }
                rollbackupdate.setBacktime(sendRed.getBacktime());
                Ret rollback = updateRedPacketLock(rollbackupdate, PayConst.RedPacketStatus.PAYING_CONFIRM, true);
                if (rollback.isFail()) {
                    return rollback;
                }
                return RetUtils.failMsg(resp.getMerMsg());
            }
        }
        sendRed.setNick(user.getNick());
        redpacketVo.setSendRed(sendRed);
        return payRedpacket(request, resp, redpacketVo, true,user);
    }

    public BasePayResp rechargeConfirm(BasePayReq payQuest, Integer uid) {
        String tranCode = PayConst.TranCode.RECHARGE_CONFIRM;
        NCountBaseVo baseVo = NCountVoUtil.getBaseVoByTranCode(tranCode, payQuest.getParams(), uid);
        Ret ret = NRequestUtils.nRequest(baseVo);
        BasePayResp resp = NRequestUtils.checkRet(ret);
        if (resp.isOk()) {
            resp.setResp(rechargeConfirmTwo(resp.getResp(), uid));
        }
        return resp;
    }

    public Map<String, Object> rechargeConfirmTwo(Map<String, Object> resp, Integer uid) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("uid", uid);
        result.put("merid", "300008795977");
        result.put("merorderid", MapUtil.getStr(resp, "ncountOrderId"));
        result.put("amount", AmountUtil.doubleAmountToInt(MapUtil.getStr(resp, "tranAmount")));
        result.put("checkdate", MapUtil.getStr(resp, "checkDate"));
        //		result.put("bankcode", MapUtil.getStr(resp, "bankCode"));
        //		result.put("cardtype", cardTypeChange(Short.parseShort(MapUtil.getStr(resp, "cardType"))));
        //		result.put("shortcardno", MapUtil.getStr(resp, "shortCardNo"));
        result.put("agrno", MapUtil.getStr(resp, "bindCardAgrNo"));
        String bizcreattime = MapUtil.getStr(resp, "submitTime");
        if (StrUtil.isNotBlank(bizcreattime)) {
            result.put("bizcreattime", DateUtil.parse(bizcreattime, "yyyyMMddHHmmss"));
        }
        result.put("ordererrormsg", errMsg(resp));
        //		result.put("bizcompletetime", DateUtil.parse(MapUtil.getStr(resp, "tranFinishTime"), "yyyyMMddHHmmss"));
        String completetime = MapUtil.getStr(resp, "tranFinishTime");
        if (StrUtil.isNotBlank(completetime)) {
            result.put("bizcompletetime", DateUtil.parse(completetime, "yyyyMMddHHmmss"));
        }
        NRequestUtils.respFormat(resp, result);
        return result;
    }

    private String errMsg(Map<String, Object> resp) {
        String errCode = MapUtil.getStr(resp, "errorCode");
        String errMsg = MapUtil.getStr(resp, "errorMsg");
        if (StrUtil.isNotBlank(errCode) && StrUtil.isNotBlank(errMsg)) {
            return errCode + ":" + errMsg;
        } else if (StrUtil.isNotBlank(errCode)) {
            return errCode;
        } else {
            return errMsg;
        }
    }

    /**
     * 修改红包主体是否加锁
     * @param redPacket
     * @param lock
     * @return
     * @author xufei
     * 2021年3月23日 上午11:12:24
     */
    public Ret updateRedPacketLock(WxWalletSendRedPacket redPacket, Short status, boolean lock) {
        if (lock) {
            ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_REDPACKET_GET_LOCK + "." + redPacket.getId(), WxWalletSendRedPacket.class);
            ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
            writeLock.lock();
            try {
                WxWalletSendRedPacket sendRed = WxWalletSendRedPacket.dao.findById(redPacket.getId());
                if (sendRed == null) {
                    return RetUtils.failMsg("操作已超时，请重试");
                }
                if (status != null && !Objects.equals(sendRed.getStatus(), status)) {
                    return RetUtils.failMsg("红包状态已发生变更");
                }
                boolean update = redPacket.update();
                if (!update) {
                    return RetUtils.failMsg("修改红包数据异常");
                }
                return RetUtils.okOper();
            } catch (Exception e) {
            } finally {
                writeLock.unlock();
            }
        } else {
            boolean update = redPacket.update();
            if (!update) {
                return RetUtils.failMsg("修改红包数据异常");
            }
            return RetUtils.okOper();
        }
        return RetUtils.failMsg("系统异常");
    }

    public Ret payRedpacket(HttpRequest request, BasePayResp resp, SendRedpacketVo redpacketVo, Boolean isAtom, User user) {
        if (!resp.isBizOk()) {
            return RetUtils.failMsg(resp.getMerMsg());
        }
        Integer offset = ConfService.getInt(Const.ConfMapping.WX_WALLET_SENDREDPACKET_TIMEOUT, 1440);
        WxWalletSendRedPacket last = null;
        if (Objects.equals(redpacketVo.getPaytype(), PayConst.RedPayType.CNY)) {
            WxWalletSendRedPacket transfer = WxWalletSendRedPacket.mapToBean(resp.getResp());
            transfer.setId(redpacketVo.getRid());
            transfer.setStatus(PayConst.RedPacketStatus.PROCESS);
            transfer.setPaytype(PayConst.RedPayType.CNY);
            ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_WALLET_COIN + "." + redpacketVo.getUid(), WxWalletCoin.class);
            ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
            writeLock.lock();
            try {
                last = WxWalletSendRedPacket.dao.findFirst("select * from wx_wallet_send_red_packet where  id = ?", redpacketVo.getRid());
                if (last == null) {
                    return RetUtils.failMsg("操作已超时，请重试");
                }

                if (Objects.equals(last.getStatus(), PayConst.RedPacketStatus.PROCESS)) {
                    return RetUtils.okData(last);
                }
                final WxWalletSendRedPacket finalLast = last;
                DateTime starttime = new DateTime();
                DateTime timeout = DateUtil.offsetMinute(starttime, offset);
                transfer.setStarttime(starttime);
                transfer.setBacktime(timeout);
                transfer.setSendmode(PayConst.RedSendMode.RESP);
                //同步钱包信息
                AbsTxAtom absTxAtom = new AbsTxAtom() {

                    @Override
                    public boolean noTxRun() {
                        Ret ret = updateRedPacketLock(transfer, PayConst.RedPacketStatus.PAYING_CONFIRM, true);
                        if (ret.isFail()) {//此处发生数据变更
                            return true;
                        }
                        Ret coinRet = coinAdd(finalLast.getCny(), finalLast.getUid(), PayConst.WalletMode.REDPACKET, Const.CoinFlag.PAY, "发红包", finalLast.getId(),
                                PayConst.WalletChangeStatus.SUCCESS, resp.getResp(), DateUtil.format(transfer.getStarttime(), "yyyy-MM-dd HH:mm:ss"),
                                DateUtil.format(transfer.getStarttime(), "yyyy-MM-dd HH:mm:ss"));
                        WxWalletCoinService.me.subCoin(finalLast.getUid(), finalLast.getCny());
                        if (coinRet.isFail()) {
                            return failRet(coinRet);
                        }
                        return true;
                    }
                };
                boolean atom;
                if (isAtom) {
                    atom = Db.use(Const.Db.TIO_SITE_MAIN).tx(absTxAtom);
                } else {
                    atom = absTxAtom.noTxRun();
                }
                if (!atom) {
                    return absTxAtom.getRetObj();
                }
                //更新红包
                last.setStatus(PayConst.RedPacketStatus.PROCESS);
                last.setStarttime(transfer.getStarttime());
                last.setBacktime(transfer.getBacktime());
                last.setPaytype(PayConst.RedPayType.CNY);
            } catch (Exception e) {
            } finally {
                writeLock.unlock();
            }
            //此处发送红包
            try {
                last = WxWalletSendRedPacket.dao.findFirst("select * from wx_wallet_send_red_packet where  id = ?", redpacketVo.getRid());
                if (Objects.equals(last.getSendmode(), PayConst.RedSendMode.RESP)) {
                    WalletQueueApi.sendRedpacketCallback(last);
                }
            } catch (Exception e) {
            }
        } else {
            last = WxWalletSendRedPacket.dao.findFirst("select * from wx_wallet_send_red_packet where  id = ?", redpacketVo.getRid());
        }

        RedissonClient redissonClient = RedisInit.get();
        last.setNick(redpacketVo.getSendRed().getNick());
        RBucket<Object> bucket = redissonClient.getBucket("redInfo_"+last.getId());
        last.setAvatar(user.getAvatar());
        bucket.set(last);
        return RetUtils.okData(last);
    }

    /**
     * 抢红包
     *
     * @param grabRedpacketVo
     * @return
     * @author xufei 2020年11月19日 上午10:45:04
     */
    @Override
    public Ret grabRedPacket(GrabRedpacketVo grabRedpacketVo, User user) {
        Ret grabret = grabRedPacketTwo(grabRedpacketVo, user);
        if (grabret.isFail()) {
            return grabret;
        }
        WxWalletRedPacketRandom random = RetUtils.getOkTData(grabret,"random");
        WxWalletSendRedPacket redPacket = RetUtils.getOkTData(grabret, "redpacket");
        BasePayResp resp = new BasePayResp();
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("uid", redPacket.getUid());
        result.put("merid", redPacket.getMerid());
        result.put("reqid", redPacket.getReqid());
        result.put("merorderid", redPacket.getMerorderid());
        result.put("payacctamount", random.getCny());
        result.put("recvacctamount",random.getCny());
        result.put("merstatus", "SUCCESS");
        resp.setOk(true);
        resp.setResp(result);
        grabRedpacketVo.setRandom(random);
        grabRedpacketVo.setRedPacket(redPacket);

        return grabRedPacketCallback(resp, grabRedpacketVo, user);
    }

    public Ret grabRedPacketTwo(GrabRedpacketVo grabVo, User user) {
        Integer rid = grabVo.getRid();
        ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_REDPACKET_GRAB + "." + rid, WxWalletGrabRedItem.class);
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
        writeLock.lock();
        try {
            RedissonClient redissonClient = RedisInit.get();
            RList<Object> list = redissonClient.getList(rid+"_redUlist");
            boolean contains = list.contains(rid+user.getId());
            if(contains) {
                return RetUtils.failMsg("您已抢过该红包");
            }
            RBucket<Object> bucket = redissonClient.getBucket("redInfo_"+rid);
            //改为缓存里拿取红包信息
            WxWalletSendRedPacket redPacket =(WxWalletSendRedPacket)bucket.get();

            if (redPacket == null) {
                return RetUtils.failMsg("红包不存在");
            }
            if (Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.SUCCESS)) {
                return RetUtils.failMsg("红包已抢完");
            }
            if (Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.TIMEOUT)) {
                return RetUtils.failMsg("红包已超时退回");
            }
            if (Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.CANCEL)) {
                return RetUtils.failMsg("红包已取消");
            }
            if (!Objects.equals(redPacket.getStatus(), PayConst.RedPacketStatus.PROCESS)) {
                return RetUtils.failMsg("红包未知状态：" + redPacket.getStatus());
            }
            WxWalletRedPacketRandom random = getRandom(redPacket);
            if (random == null) {
                return RetUtils.failMsg("红包已抢完");
            }
            if (Objects.equals(redPacket.getChatmode(), Const.ChatMode.P2P) && !Objects.equals(random.getUid(), user.getId())) {
                return RetUtils.failMsg("你不能抢自己的红包");
            }
            list.add(rid+user.getId());
            random.setStatus(PayConst.RedRandomStatus.RANDOM);
            random.setUid(user.getId());
            Short acceptnum = redPacket.getAcceptnum();
            if(acceptnum == null) {
                acceptnum= 0;
            }
            redPacket.setAcceptnum((short)(acceptnum+1));
            //如果数字一样 说明抢完了 更新
            if(redPacket.getAcceptnum() == redPacket.getNum()) {
                redPacket.setStatus(PayConst.RedPacketStatus.SUCCESS);
                redPacket.setEndtime(new Date());
                redPacket.update();
            }
            bucket.set(redPacket);
            Ret ok = Ret.ok("random", random);
            return ok.set("redpacket", redPacket);
        } catch (Exception e) {
        } finally {
            writeLock.unlock();
        }
        return RetUtils.failMsg("系统错误");
    }

    /**
     * @param redpacket
     * @return
     * @author xufei
     * 2021年3月17日 上午10:48:19
     */
    private WxWalletRedPacketRandom getRandom(WxWalletSendRedPacket redpacket) {
        RedissonClient redissonClient = RedisInit.get();
        RList<Object> list = redissonClient.getList("redRandom_"+redpacket.getId());
        if (list == null || list.size()<=0) {
            return null;
        }
        Collections.shuffle(list);
        WxWalletRedPacketRandom random =(WxWalletRedPacketRandom)list.get(0);
        list.remove(0);
        return random;
    }

    public Ret grabRedPacketCallback(BasePayResp resp, GrabRedpacketVo grabVo,User user) {
        if (!resp.isBizOk()) {
            return RetUtils.failMsg(resp.getMerMsg());
        }
        WxWalletRedPacketRandom random = grabVo.getRandom();
        WxWalletSendRedPacket redPacket = grabVo.getRedPacket();
        DateTime time = new DateTime();
        WxWalletGrabRedItem redItem = WxWalletGrabRedItem.mapToBean(resp.getResp());
        redItem.setCny(random.getCny());
        redItem.setUid(random.getUid());
        redItem.setChatbizid(grabVo.getBizid());
        redItem.setChatmode(grabVo.getChatmode());
        redItem.setRid(redPacket.getId());
        redItem.setSenduid(redPacket.getUid());
        redItem.setStatus(PayConst.RedRandomStatus.SUCCESS);
        redItem.setSendwalletid(redPacket.getUid().toString());
        redItem.setRemark(redPacket.getRemark());
        redItem.setGrabtime(time);
        redItem.setRandomid(random.getId());
        redItem.setNick(user.getNick());
        redItem.setAvatar(user.getAvatar());
        RedissonClient redissonClient = RedisInit.get();
        RList<Object> list = redissonClient.getList("grabRedItem_"+redPacket.getId());
        list.add(redItem);
        ReentrantReadWriteLock rwLock = LockUtils.getReentrantReadWriteLock(org.tio.sitexxx.service.utils.LockUtils.LockPrefix.CHAT_WALLET_COIN + "." + grabVo.getUid(), WxWalletCoin.class);
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
        writeLock.lock();
        try {
            AbsTxAtom absTxAtom = new AbsTxAtom() {

                @Override
                public boolean noTxRun() {
                    boolean save = redItem.save();
                    WxWalletCoinService.me.addCoid(random.getUid(),random.getCny());
                    if (!save) {
                        return failRet("抢红包信息保存失败");
                    }
                    WxWalletRedPacketRandom position = new WxWalletRedPacketRandom();
                    position.setId(random.getId());
                    position.setStatus(PayConst.RedRandomStatus.SUCCESS);
                    position.setAccounttime(time);
                    boolean update = position.update();
                    if (!update) {
                        return failRet("随机红包确认失败");
                    }
                    String redTimeStr = DateUtil.format(redItem.getGrabtime(), "yyyy-MM-dd HH:mm:ss");
                    Ret coinRet = coinAdd(redItem.getCny(), redItem.getUid(), PayConst.WalletMode.REDPACKET, Const.CoinFlag.INCOME, "抢红包", redItem.getId(),
                            PayConst.WalletChangeStatus.SUCCESS, resp.getResp(), redTimeStr, redTimeStr);
                    if (coinRet.isFail()) {
                        return failRet(coinRet);
                    }
                    return okRet(redItem);
                }
            };
            Db.use(Const.Db.TIO_SITE_MAIN).tx(absTxAtom);
        } catch (Exception e) {
        } finally {
            writeLock.unlock();
        }
        return RetUtils.okData(redItem);
    }

    public Ret coinAdd(Long cny, Integer uid, Short mode, Short coinflag, String remark, Integer bizid,
                       Short status, Map<String, Object> resultMap, String starttime, String endtime) {
        WxWalletCoinItem coinItem = new WxWalletCoinItem();
        coinItem.setUid(uid);
        coinItem.setMode(mode);
        coinItem.setCoinflag(coinflag);
        coinItem.setStatus(status);
        coinItem.setCny(cny);
        coinItem.setOrderstatus(MapUtil.getStr(resultMap, "merstatus"));
        coinItem.setBizid(bizid);
        coinItem.setReqid(MapUtil.getStr(resultMap, "reqid"));
        coinItem.setMerorderid(MapUtil.getStr(resultMap, "merorderid"));
        coinItem.setBizcompletetime(endtime);
        coinItem.setBizcreattime(starttime);
        coinItem.setRemark(remark);
        boolean save = coinItem.save();
        if (!save) {
            return RetUtils.failMsg("保存明细失败");
        }
        return RetUtils.okData(coinItem);
    }

    /**
     * 初始化子红包
     * @param rid
     * @param cny
     * @param packetType
     * @param index
     * @return
     * @author xufei
     * 2021年3月16日 下午1:56:57
     */
    private static boolean initRedRandom(Integer rid, Integer touid, Long cny, Short packetType, Short index) {
        WxWalletRedPacketRandom random = new WxWalletRedPacketRandom();
        random.setRid(rid);
        if (touid != null) {
            random.setUid(touid);
        }
        if(cny <=0) {
            return false;
        }
        random.setCny(cny);
        random.setMode(packetType);
        random.setRedindex(index);
        if (!random.save()) {
            return false;
        }
        RedissonClient redissonClient = RedisInit.get();
        RList<Object> list = redissonClient.getList("redRandom_"+rid);
        list.add(random);
        return true;
    }
}

