package com.ytpm.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ytpm.advertise.enums.AdSourceTypeEnum;
import com.ytpm.agent.enums.UserStatusEnum;
import com.ytpm.agent.model.YtApp;
import com.ytpm.agent.model.YtPlatformBanned;
import com.ytpm.agent.model.YtPlatformDeblocking;
import com.ytpm.agent.model.YtPlatformUserApp;
import com.ytpm.agent.view.AgentAppClassView;
import com.ytpm.agent.view.AgentEnableAppView;
import com.ytpm.agent.view.AgentUserInfo;
import com.ytpm.app.enums.LoginType;
import com.ytpm.app.model.YtDyzAdRecord;
import com.ytpm.app.model.YtDyzLoginRecord;
import com.ytpm.app.model.YtDyzUser;
import com.ytpm.app.param.LoginRiskYtDyzUserParam;
import com.ytpm.app.view.RiskYtDyzUserView;
import com.ytpm.custom.CustomField;
import com.ytpm.dao.AppMapper;
import com.ytpm.dao.DictMapper;
import com.ytpm.dao.RiskConfigMapper;
import com.ytpm.dao.RiskManageMapper;
import com.ytpm.dao.RiskUserMapper;
import com.ytpm.general.RepMessage;
import com.ytpm.general.Result;
import com.ytpm.general.ResultTable;
import com.ytpm.general.StatusCode;
import com.ytpm.handle.CommonException;
import com.ytpm.handle.CustomerException;
import com.ytpm.risk.enums.BannedTypeEnum;
import com.ytpm.risk.enums.EffectNodeEnum;
import com.ytpm.risk.model.YtRiskConfig;
import com.ytpm.risk.model.YtRiskTemplate;
import com.ytpm.risk.param.RiskBannedListParam;
import com.ytpm.risk.param.RiskBannedParam;
import com.ytpm.risk.param.RiskConfigListParam;
import com.ytpm.risk.param.RiskConfigParam;
import com.ytpm.risk.param.RiskDeblockingListParam;
import com.ytpm.risk.param.RiskRelativeAppParam;
import com.ytpm.risk.param.RiskUserStatusParam;
import com.ytpm.risk.view.RiskBannedListView;
import com.ytpm.risk.view.RiskConfigListView;
import com.ytpm.risk.view.RiskConfigView;
import com.ytpm.risk.view.RiskDeblockingListView;
import com.ytpm.risk.view.RiskTemplateView;
import com.ytpm.service.RiskService;
import com.ytpm.util.FeignClientInvoker;
import com.ytpm.util.IPUtil;
import com.ytpm.util.RedisService;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j(topic = "风控服务")
@Service
@RefreshScope
public class RiskServiceImpl implements RiskService {

    @Resource
    private FeignClientInvoker feignInvoker;
    @Resource
    private DictMapper  dictMapper;
    @Resource
    private RiskConfigMapper configMapper;
    @Resource
    private RiskUserMapper riskUserMapper;
    @Resource
    private RiskManageMapper riskManageMapper;
    @Resource
    private RedisService redisService;
    @Resource
    private AppMapper appMapper;
    @Resource
    private ScheduledExecutorService scheduledExecutorService;

    @Value("${risk.banned.tips}")
    private String tips;
    @Value("${risk.banned.ios-ditch-tip:}")
    private String iosDithTip;
    @Value("${risk.banned.ios-ipLimit-tip:}")
    private String iosIpLimitTip;
    @Value("${risk.banned.defaultLimit}")
    private Integer defaultLimit;
    @Value("${risk.banned.defaultReason}")
    private String defaultReason;
    @Value("${risk.visitor.validity-period:48}")
    private Integer loginCheckValidityPeriod;


    private static Set<String> ipWhiteList = new CopyOnWriteArraySet<>();

    /**
     * 查询配置字段选项
     */
    @Override
    public ResultTable<RiskConfigView> getRiskConfig(String appIds) {
        List<AgentAppClassView> viewList = configMapper.getAppClazz(appIds);
        List<RiskConfigView> configList = new ArrayList<>();
        try{
            for (AgentAppClassView view : viewList) {
                addConfigItem(Class.forName(view.getFullName()).getDeclaredFields(),configList,null);
            }
        }catch (Exception e){
            log.error(e.getMessage());
            throw new CustomerException(e.getMessage());
        }
        return ResultTable.resultTableOk(new PageInfo<RiskConfigView>(configList));
    }

    /**
     * 根据业务节点查询配置项
     */
    @Override
    public ResultTable<RiskConfigView> getFieldConfigByNode(Integer effectNode) {
        List<AgentAppClassView> viewList = configMapper.getAppClazz("");
        List<RiskConfigView> configList = new ArrayList<>();
        Set<String> clazzSet = viewList.stream().map(AgentAppClassView::getFullName).collect(Collectors.toSet());
        try{
            for (String fullName : clazzSet) {
                addConfigItem(Class.forName(fullName).getDeclaredFields(),configList,effectNode);
            }
        }catch (Exception e){
            log.error(e.getMessage());
            throw new CustomerException(e.getMessage());
        }
        return ResultTable.resultTableOk(new PageInfo<RiskConfigView>(configList));
    }

    /**
     * 添加配置项
     */
    private void addConfigItem(Field[] fields, List<RiskConfigView> configList, Integer effectNode) {
        RiskConfigView view;
        for (Field field : fields) {
            field.setAccessible(true);
            if((Objects.nonNull(effectNode) && field.getAnnotation(CustomField.class).node()!=99 &&
                    field.getAnnotation(CustomField.class).node() != effectNode)||
                    !field.isAnnotationPresent(CustomField.class)
            )continue;
            view = new RiskConfigView();
            view.setFieldName(field.getName());
            if(CharSequenceUtil.isNotBlank(field.getAnnotation(CustomField.class).value())){
                view.setDictList(dictMapper.getByTypeCode(field.getAnnotation(CustomField.class).value()));
            }
            view.setFieldDesc(field.getAnnotation(ApiModelProperty.class).value());
            configList.add(view);
        }
    }

    /**
     * 保存风控配置
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> saveConfig(RiskConfigParam param) {
        List<RiskConfigListParam> configList = param.getConfigList();
        if(CollUtil.isEmpty(param.getConfigList())){
            return Result.resultErr(RepMessage.COLLECTION_EMPTY);
        }
        List<YtRiskConfig> configs = new ArrayList<>();
        YtRiskConfig config;
        StringBuilder content = new StringBuilder();
        for (RiskConfigListParam listVO : configList) {
            listVO.setConfigId(IdUtil.fastSimpleUUID());
            config = new YtRiskConfig();
            BeanUtil.copyProperties(listVO, config);
            config.setChannelId(param.getChannelId());
            String format = String.format(config.getFieldDesc(), config.getConfigVal());
            if(CharSequenceUtil.isNotBlank(format)){
                content.append(format);
                if(configList.indexOf(listVO)!= (configList.size() - 1)){
                    content.append(",");
                }
            }
            configs.add(config);
        }
        configMapper.insertAll(configs);
        addTemplateAndRelative(param,content.toString(),configs);
        return Result.resultOk(RepMessage.SAVE_SUCCESS);
    }

    /**
     * 新增模版和关联配置
     */
    private void addTemplateAndRelative(RiskConfigParam param,String content,List<YtRiskConfig> configs) {
        YtRiskTemplate template = new YtRiskTemplate();
        BeanUtil.copyProperties(param,template);
        template.setTemplateId(IdUtil.fastSimpleUUID());
        template.setTemplateContent(content);
        template.setEnabled(0);
        template.setCreateUserId(param.getChannelId());
        template.setCreateTime(new Date());
        configMapper.saveTemplate(template);

        configMapper.addRelease(template.getTemplateId(),
                configs.stream().map(YtRiskConfig::getConfigId).collect(Collectors.toList()),
                param.getOperator(),param.getOperatorName()
        );
    }


    /**
     * 锁定用户禁止用户登录
     */
    @Override
    public Result<?> lockUser(String userId,String appId, AgentUserInfo userInfo) {
        //通过redis进行业务分离， 再各个子服务中进行锁定和解锁，解耦feign调用
        redisService.setTimeOutStr("lock_"+userId,appId,1000);
        RiskBannedParam param = new RiskBannedParam();
        param.setUserId(userId);
        param.setAppId(appId);
        param.setBannedReason(BannedTypeEnum.CHANNEL.getDesc()+"禁止登录");
        param.setOperatorName(userInfo.getNickName());
        param.setOperator(userInfo.getUserId());
        param.setAgentId(userInfo.getUserId());
        param.setBannedType(BannedTypeEnum.CHANNEL.getCode());
        addBannedRecord(Collections.singletonList(userId),param);
        //预设24小时过期key 用于解锁用户
        redisService.setTimeOutHoursStr("unlock_"+userId,appId,24);
        return Result.resultOk(RepMessage.LOCK_USER_SUCCESS);
    }

    /**
     * 封禁用户
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result<?> banned(RiskBannedParam param) {
        redisService.setTimeOutStr("lock_"+param.getUserId(),param.getAppId(),1000);
        addBannedRecord(Collections.singletonList(param.getUserId()),param);
        //预设24小时过期key 用于解锁用户
        redisService.setTimeOutHoursStr("unlock_"+param.getUserId(),param.getAppId(),param.getBannedLimit() * 24L);
        return Result.resultOk(RepMessage.ALREADY_RISK_USER);
    }

    /**
     * 查询风控配置列表
     */
    @Override
    public ResultTable<RiskConfigListView> getRiskConfigList(RiskConfigParam param) {
        PageHelper.startPage(param.getPage(),param.getLimit());
        return ResultTable.resultTableOk(new PageInfo<RiskConfigListView>(configMapper.getRiskConfigList(param)));
    }

    /**
     * 查询风控记录
     */
    @Override
    public ResultTable<RiskBannedListView> bannedList(RiskBannedListParam param) {
        PageHelper.startPage(param.getPage(),param.getLimit());
        List<RiskBannedListView> bannedList = riskManageMapper.getBannedList(param);
        if(CollUtil.isNotEmpty(bannedList)){
//            setBannedLoginInfo(bannedList);
        }
        return ResultTable.resultTableOk(new PageInfo<RiskBannedListView>(bannedList));
    }


    /**
     * 查询解封记录
     */
    @Override
    public ResultTable<RiskDeblockingListView> deblockingList(RiskDeblockingListParam param) {
        PageHelper.startPage(param.getPage(),param.getLimit());
        List<RiskDeblockingListView> deblockingList = riskManageMapper.getDeblockingList(param);
        if(CollUtil.isNotEmpty(deblockingList)){
//            setDeblockLoginInfo(deblockingList,param);
        }
        return ResultTable.resultTableOk(new PageInfo<RiskDeblockingListView>(deblockingList));
    }

    /**
     * 修改用户状态
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> changeUserStatus(RiskUserStatusParam param) {
        //如果原账号状态正常且本次操作状态不正常
        modifyAppUserInfo(param.getUserId(),param);
        return Result.resultOk(RepMessage.USER_STATUS_MODIFY);
    }

    /**
     * 风控配置关联应用
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> relativeApp(RiskRelativeAppParam param) {
        List<AgentEnableAppView> appList = param.getAppList();
        if(CollUtil.isEmpty(appList)){
            return Result.resultErr(RepMessage.COLLECTION_EMPTY);
        }
        riskManageMapper.deleteRelatives(param.getTemplateId());
        riskManageMapper.relativeApps(param);
        return Result.resultOk(RepMessage.RELATIVE_APP_SUCCESS);
    }

    /**
     * 启用或关闭风控配置
     */
    @Override
    public Result<?> isEnabledConfig(String templateId, String userId) {
        YtRiskTemplate template = configMapper.selectOneTemplate(templateId);
        if(Objects.isNull(template)){
            return Result.resultErr(RepMessage.OBJECT_NOT_EXIST);
        }
        Integer enabled = template.getEnabled();

        YtRiskTemplate newTemp = new YtRiskTemplate();
        newTemp.setTemplateId(templateId);
        newTemp.setUpdateUserId(userId);
        newTemp.setUpdateTime(new Date());
        newTemp.setEnabled(enabled == 0?1:0);
        configMapper.updateByTempId(newTemp);
        return Result.resultOk(RepMessage.MODIFY_SUCCESS);
    }

    /**
     * 风控配置模板详情
     */
    @Override
    public Result<RiskTemplateView> templateView(String templateId, String channelId) {
        return Result.resultObjOk(configMapper.getTemplateView(templateId,channelId));
    }

    /**
     * 修改风控配置模版
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<?> updateConfig(RiskConfigParam param) {
        YtRiskTemplate old = configMapper.selectOneTemplate(param.getTemplateId());
        if(Objects.isNull(old)){
            return Result.resultErr(RepMessage.OBJECT_NOT_EXIST);
        }
        //修改配置值
        List<RiskConfigListParam> configList = param.getConfigList();
        StringBuilder content = new StringBuilder();
        for (RiskConfigListParam listParam : configList) {
            YtRiskConfig config = new YtRiskConfig();
            config.setConfigId(listParam.getConfigId());
            config.setConfigVal(listParam.getConfigVal());
            configMapper.updateConfigVal(config);
            String format = String.format(listParam.getFieldDesc(), listParam.getConfigVal());
            if(CharSequenceUtil.isNotBlank(format)){
                content.append(format).append(",");
            }
        }
        //同步修改配置模版内容
        YtRiskTemplate template = new YtRiskTemplate();
        BeanUtil.copyProperties(param,template);
        template.setTemplateContent(content.toString());
        template.setUpdateTime(new Date());
        template.setUpdateUserId(param.getChannelId());
        configMapper.updateByTempId(template);
        return Result.resultOk(RepMessage.MODIFY_SUCCESS);
    }

    /**
     * 登录检查默认风控配置
     */
    @Override
    public Result<?> checkRisk(YtDyzUser dyzUser) {
        //查询当前用户今日有被解封则直接放行
        List<RiskDeblockingListView> vo = riskManageMapper.queryTodayDeblock(dyzUser.getUserId());
        if(CollUtil.isNotEmpty(vo)){
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        // ip白名单用户直接放行
        if (checkDyzUserIp(dyzUser)) {
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        RiskTemplateView view = configMapper.getByCode(dyzUser.getRiskCode());
        //如果启用了该配置 校验默认的风控配置
        if (1 == view.getEnabled()) {
            checkDefaultRiskConfig(dyzUser, view.getConfigList());
        }
        // 风控 尝试聚合查询
        List<YtDyzUser> dyzUsers = null;
        Integer userCount = null;
        Integer cityUserCount = null;
        Long sameDeviceCount = null;
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        RiskYtDyzUserView riskYtDyzUserView = queryParamByUnified(dyzUser, ytApp);
        if (riskYtDyzUserView != null) {
            dyzUsers = riskYtDyzUserView.getUserList();
            sameDeviceCount = riskYtDyzUserView.getSameDeviceCount();
            userCount = riskYtDyzUserView.getUserCount();
            cityUserCount = riskYtDyzUserView.getCityUserCount();
        }
        checkRisk322(dyzUser, dyzUsers, sameDeviceCount);
        // ios端 校验ip风控
        if (ytApp.getAppType() == 2) {
            checkRisk334(dyzUser, userCount);
            checkRisk335(dyzUser, cityUserCount);
        }
        //查询用户所在app是否配置其他风控规则
//        checkCustomRisk(dyzUser,EffectNodeEnum.LOGIN.getNode(),null);
        return Result.resultOk(RepMessage.QUERY_SUCCESS);
    }

    private RiskYtDyzUserView queryParamByUnified(YtDyzUser dyzUser, YtApp ytApp){
        RiskYtDyzUserView riskYtDyzUserView = null;
        RiskTemplateView riskTempView334 = configMapper.getByCode(dyzUser.getAppId() + "-334");
        RiskTemplateView riskTempView335 = configMapper.getByCode(dyzUser.getAppId() + "-335");
        RiskTemplateView riskTempView322 = configMapper.getByCode("322");

        if (riskTempView334 != null && riskTempView334.getEnabled() == 1
                || riskTempView335 != null && riskTempView335.getEnabled() == 1
                || riskTempView322 != null && riskTempView322.getEnabled() == 1) {
            YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
            if (ytApp.getAppType() == 2) {
                LoginRiskYtDyzUserParam param = new LoginRiskYtDyzUserParam();
                param.setIosId(dyzUser.getIosId());
                param.setAppId(dyzUser.getAppId());
                param.setPhoneJson(dyzUser.getPhoneJson());
                // 传递322风控限制参数
                if (riskTempView322 != null && riskTempView322.getEnabled() == 1) {
                    List<RiskConfigView> configList = riskTempView322.getConfigList();
                    Map<String, String> configMap = configList.stream().collect(
                            Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
                    int days = Integer.parseInt(configMap.get("days"));
                    param.setLimitDays322(days);
                }
                // 传递332风控限制参数
                if (riskTempView334 != null && riskTempView334.getEnabled() == 1) {
                    param.setLastLoginIp(dyzUser.getLastLoginIp());
                    List<RiskConfigView> configList = riskTempView334.getConfigList();
                    Map<String, String> configMap = configList.stream().collect(
                            Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
                    int limitHour = Integer.parseInt(configMap.get("limitHour"));
                    param.setLimitHour(limitHour);
                }
                // 传递335风控限制参数
                if (riskTempView335 != null && riskTempView335.getEnabled() == 1) {
                    List<RiskConfigView> configList = riskTempView335.getConfigList();
                    Map<String, String> configMap = configList.stream().collect(
                            Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
                    String limitCity = configMap.get("limitCity");
                    int limitHour = Integer.parseInt(configMap.get("limitHour"));
                    // 当前用户的登陆IP包含在限制地区内，则查询地区登陆用户数
                    if (StringUtils.isNotEmpty(limitCity) && StringUtils.isNotEmpty(dyzUser.getLastLoginIpAddress())) {
                        for (String city : limitCity.split(",")) {
                            if (dyzUser.getLastLoginIpAddress().contains(city)) {
                                param.setLimitCity(limitCity);
                                param.setLimitHour335(limitHour);
                                break;
                            }
                        }
                    }
                }
                try {
                    Object object = feignInvoker.invoke(userApp.getServiceName(), "queryUserByParam", param);
                    if (object != null) {
                        riskYtDyzUserView = JSONObject.parseObject(JSON.toJSONString(object), RiskYtDyzUserView.class);
                    }
                } catch (Exception e) {
                    log.warn(StringUtils.format("feign调用{}.queryUserByParam失败：{}",
                            userApp.getServiceName(), e.getMessage()), e);
                }
            }
        }
        return riskYtDyzUserView;
    }

    private void checkRisk335(YtDyzUser dyzUser, Integer cityUserCount) {
        // 相同IP城市登陆用户数限制
        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-335");
        if (riskTempView == null || riskTempView.getEnabled() != 1) {
            return;
        }
        if (cityUserCount == null) {
            return;
        }
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());

        List<RiskConfigView> configList = riskTempView.getConfigList();
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        int limitHour = Integer.parseInt(configMap.get("limitHour"));
        int limitCount = Integer.parseInt(configMap.get("limitCityCount"));
        String limitCity = configMap.get("limitCity");
        if (StringUtils.isEmpty(limitCity)) {
            return;
        }
        // 已通过的用户放行
        String passkey = StrUtil.format("riskPass:335:{}:{}", userApp.getAppId(), limitCity);
        Set<String> passUserSet = new HashSet<>();
        if (redisService.hasKey(passkey)) {
            passUserSet.addAll(Arrays.asList(redisService.getStr(passkey).split(",")));
        }
        if (CollUtil.isNotEmpty(passUserSet) && passUserSet.contains(dyzUser.getUserId())) {
            return;
        }
        if (redisService.hasKey(passkey) && limitCount <= cityUserCount) {
            log.warn(StrUtil.format("[risk 335] superiorId:{} appId:{} userId:{} limitCityCount:{}",
                    userApp.getAppId(), dyzUser.getAppId(), dyzUser.getUserId(), limitCount));
            String errorMsg = RepMessage.RISK_IP_LIMIT_VALUE;
            if (StrUtil.isNotEmpty(iosIpLimitTip)) {
                String[] split = iosIpLimitTip.split(",");
                errorMsg = split[RandomUtil.randomInt(split.length)];
            }
            try {
                riskLockUser(dyzUser, "335", StrUtil.format("{}小时内{}登陆账号超过限制", limitHour, limitCity),
                        errorMsg, limitHour);
            } catch (Exception e) {
                throw new CustomerException(errorMsg);
            }
        }
        passUserSet.add(dyzUser.getUserId());
        redisService.setTimeOutHoursStr(passkey, String.join(",", passUserSet), 24);
    }

    private void checkRisk334(YtDyzUser dyzUser, Integer userCount) {
        // 相同IP登陆多个账号风控限制
        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-334");
        if (riskTempView == null || riskTempView.getEnabled() != 1) {
            return;
        }
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
        String lastLoginIp = dyzUser.getLastLoginIp();
        // 已通过的用户放行
        String passkey = StrUtil.format("riskPass:334:{}:{}", userApp.getAppId(), dyzUser.getLastLoginIp());
        if (redisService.hasKey(passkey) && StrUtil.equals(dyzUser.getUserId(), redisService.getStr(passkey))) {
            return;
        }
        List<RiskConfigView> configList = riskTempView.getConfigList();
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        int limitCount = Integer.parseInt(configMap.get("limitCount"));
        int limitHour = Integer.parseInt(configMap.get("limitHour"));
        if (userCount == null) {
            userCount = 0;
            Object object = feignInvoker.invoke(userApp.getServiceName(), "queryCountByIpTime",
                    dyzUser.getAppId(), lastLoginIp, limitHour);
            JSONObject jsonObject = object == null ? null : JSON.parseObject(JSON.toJSONString(object));
            if (jsonObject != null && jsonObject.get("data") != null) {
                userCount = Integer.parseInt(jsonObject.get("data").toString());
            }
        }
        if (redisService.hasKey(passkey) && limitCount <= userCount) {
            log.warn(StrUtil.format("[risk 334] superiorId:{} appId:{} userId:{} limitCount:{}",
                    userApp.getAppId(), dyzUser.getAppId(), dyzUser.getUserId(), limitCount));
            String errorMsg = RepMessage.RISK_IP_LIMIT_VALUE;
            if (StrUtil.isNotEmpty(iosIpLimitTip)) {
                String[] split = iosIpLimitTip.split(",");
                errorMsg = split[RandomUtil.randomInt(split.length)];
            }
            try {
                riskLockUser(dyzUser, "334", "同IP24小时内登陆多个账号", errorMsg);
            } catch (Exception e) {
                throw new CustomerException(errorMsg);
            }
        }
        redisService.setTimeOutHoursStr(passkey, dyzUser.getUserId(), 24);
    }

    private String getTipsMsg(){
        String[] split = tips.split(",");
        return split[RandomUtil.randomInt(split.length)];
    }
    /**
     * 校验风控规则322
     */
    private void checkRisk322(YtDyzUser dyzUser, List<YtDyzUser> dyzUsers, Long sameDeviceCount) {
        // ip白名单用户直接放行
        if (checkDyzUserIp(dyzUser)) {
            return;
        }
        RiskTemplateView view = configMapper.getByCode("322");
        //根据用户所属应用查询该应用母包openid查询用户信息
        if (dyzUsers == null) {
            YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
            YtPlatformUserApp userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
            Object o;
            if (dyzUser.getIosId() != null) {
                o = feignInvoker.invoke(userApp.getServiceName(),"queryByIosId",dyzUser.getIosId());
            } else {
                o = feignInvoker.invoke(userApp.getServiceName(),"queryByOpenid",dyzUser.getWxOpenId());
            }
            dyzUsers = JSONArray.parseArray(JSON.toJSONString(o), YtDyzUser.class);
        }
        List<RiskConfigView> configList = view.getConfigList();
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        //判断同应用 UID生成数如果不小于配置数则触发规则校验
        int uidCount = Integer.parseInt(configMap.get("uidCount"));
        int days = Integer.parseInt(configMap.get("days"));
        //过滤该用户注册时间在三天内的渠道数
        long ditchCount = dyzUsers.stream().filter(
                s -> (days > DateUtil.between(new Date(), s.getRegistryTime(), DateUnit.DAY))
        ).count();
        //三天内注册的渠道数小于预设的渠道数通过校验，否则风控锁定用户
        if (ditchCount < uidCount  && (sameDeviceCount == null || sameDeviceCount <= 1)) return;
        String tips = getTipsMsg();
        if (dyzUser.getLoginType() != null && LoginType.IOS == dyzUser.getLoginType() || StrUtil.isNotEmpty(dyzUser.getIosId())) {
            tips = StrUtil.isNotEmpty(iosDithTip) ? iosDithTip : tips;
        }
        riskLockUser(dyzUser, "322", "系统判定重复刷单用户限制", tips);
    }

    private boolean checkDyzUserIp(YtDyzUser dyzUser) {
        String cacheKey = "risk:whiteIps";
        if (!redisService.hasKey(cacheKey) || StrUtil.isEmpty(dyzUser.getLastLoginIp())) {
            return false;
        }
        return IPUtil.isIpInWhitelist(dyzUser.getLastLoginIp(), Arrays.asList(redisService.getStr(cacheKey).split(",")));
    }

    /**
     * 校验默认风控配置， 固定配置值不改
     *  days ?天内为1
     *  ipPrefix IP前?段为4
     *  ipSameCount 同IP登录数量为>2
     */
    private void checkDefaultRiskConfig(YtDyzUser dyzUser, List<RiskConfigView> configList) {
        //封装配置值map
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        //封装用户值map
        List<YtDyzLoginRecord> recordList = dyzUser.getLoginRecordList();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, -Integer.parseInt(configMap.get("days")));
        Date yesterday = instance.getTime();
        //拿到同IP登录次数
        List<YtDyzLoginRecord> dayLoginList = recordList.stream().filter(
                s -> s.getLoginTime().after(yesterday) &&
                        s.getLoginTime().before(new Date())).collect(Collectors.toList());
        int intCount = 0;
        if(!dayLoginList.isEmpty() && configMap.get("ipPrefix") != null){
            List<String>  ipaddrList = dayLoginList.stream()
                    .map(YtDyzLoginRecord::getLoginIp).distinct()
                    .filter(StrUtil::isNotEmpty)
                    .collect(Collectors.toList());
            List<String> ipResult = new ArrayList<>();
            int ipPrefix = Integer.parseInt(configMap.get("ipPrefix"));
            for (String ipaddr : ipaddrList) {
                ipResult.add(ipaddr.substring(0, ipPrefix));
            }
            intCount = Math.toIntExact(ipResult.size());
        }
        int ipSameCount = Integer.parseInt(configMap.get("ipSameCount"));
        if(intCount > ipSameCount){//校验同IP登录次数大于预设值，锁定用户
            riskLockUser(dyzUser,dyzUser.getRiskCode(),"系统判定重复刷单用户限制",getTipsMsg());
        }
    }

    /**
     * 根据appId查询应用配置模版
     */
    @Override
    public Result<RiskTemplateView> getAppConfig(String appId) {
        return Result.resultObjOk(configMapper.getAppConfig(appId));
    }

    /**
     * 根据风控编码获取风控配置
     */
    @Override
    public Result<RiskTemplateView> getRiskByCode(String code) {
        return Result.resultObjOk(configMapper.getByCode(code));
    }

    /**
     * 校验广告的默认风控配置
     */
    @Override
    public Result<?> checkAdRisk(YtDyzUser dyzUser) {
        //查询当前用户今日有被解封则直接放行
        long start = System.currentTimeMillis();
        List<RiskDeblockingListView> vo = riskManageMapper.queryTodayDeblock(dyzUser.getUserId());
        if(CollUtil.isNotEmpty(vo)){
            log.debug(StrUtil.format("[checkAdRisk] appId:{} (end - start):{}",
                    dyzUser.getAppId(), System.currentTimeMillis() - start));
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp userApp =  appMapper.selectParentApp(ytApp.getSuperiorId());
        Object o = feignInvoker.invoke(userApp.getServiceName(), "adRecords", dyzUser.getUserId(),
                AdSourceTypeEnum.rewarded_video.getAdSourceType());
        JSONObject object = JSON.parseObject(JSON.toJSONString(o));
        List<YtDyzAdRecord> adRecords = JSONArray.parseArray(object.getString("data"), YtDyzAdRecord.class);
        if(CollUtil.isEmpty(adRecords)){
            log.debug(StrUtil.format("[checkAdRisk] appId:{} (end - start):{}",
                    dyzUser.getAppId(), System.currentTimeMillis() - start));
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        try {
            // 校验风控767规则 月记录数
            checkRisk767(dyzUser, adRecords, userApp);
            // 校验风控768规则，小时内记录数
            checkRisk768(dyzUser, adRecords);
            //过滤出当日的激励视频数据 按照时间正序排以便筛选每日前几条激励视频
            adRecords = adRecords.stream().filter(s ->
                    DateUtil.isSameDay(DateUtil.parse(s.getFinishTime()), new Date())
            ).sorted(Comparator.comparing(YtDyzAdRecord::getFinishTime)).collect(Collectors.toList());
            //校验风控742规则 当日观看视频数已经达到风控条件预设的视频数
            checkRisk742(dyzUser, adRecords);
            if (!adRecords.isEmpty() && adRecords.size() > 1) {
                //校验风控746规则 判断当日获得收益的广告达到预设数值
                checkRisk746(dyzUser, adRecords);
                // 校验风控766规则 当日用户获得{}个获得奖励的激励视频，近期前{}个获得奖励的激励视频平均收益<{}
                checkRisk766(dyzUser, adRecords);
            }
        } catch (CommonException e) {
            log.debug(StrUtil.format("[checkAdRisk] appId:{} (end - start):{}",
                    dyzUser.getAppId(), System.currentTimeMillis() - start));
            return Result.resultFail(StatusCode.ACCESS_RISK_ERROR, e.getMessage());
        }
        log.debug(StrUtil.format("[checkAdRisk] appId:{} (end - start):{}",
                dyzUser.getAppId(), System.currentTimeMillis() - start));
        return Result.resultOk(RepMessage.QUERY_SUCCESS);
    }

    /**
     * 校验默认风控规则768
     * {hourCount}小时内，获得奖励的激励视频数>{hourTaskLimit}
     */
    private void checkRisk768(YtDyzUser dyzUser, List<YtDyzAdRecord> adRecords) {
        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-768");
        if (riskTempView == null || riskTempView.getEnabled() != 1) return;
        Map<String, String> riskConfigMap = getRsihConfigMap(riskTempView);
        int hourCount = Integer.parseInt(riskConfigMap.getOrDefault("hourCount", "0"));
        int hourTaskLimit = Integer.parseInt(riskConfigMap.getOrDefault("hourTaskLimit", "0"));
        if (hourCount > 0 && hourTaskLimit > 0) {
            Date limitStartDate = DateUtil.offset(new Date(), DateField.HOUR, -hourCount);
            List<YtDyzAdRecord> revenues = adRecords.stream()
                    .filter(
                            s -> Objects.nonNull(s.getRevenue()) && (s.getRevenue().compareTo(BigDecimal.ZERO)) > 0)
                    .filter(item -> DateUtil.parse(item.getFinishTime()).after(limitStartDate))
                    .collect(Collectors.toList());
            if (revenues.size() > hourTaskLimit) {
                String format = StrUtil.emptyToDefault(dyzUser.getPowerLimitTip(), "当前设备完成任务次数过多，请{}后重试！");
                log.warn(StrUtil.format("[risk 768]appId:{} userId:{} adRecordCount:{} hourLimit:{}",
                        dyzUser.getAppId(),dyzUser.getUserId(), revenues.size(), hourTaskLimit));
                throw new CommonException(StrUtil.format(format,StrUtil.format("{}小时", hourCount)));
            }
        }
    }

    /**
     * 校验默认风控规则767
     * {monthCount}月内，获得奖励的激励视频数>{monthTaskLimit}
     */
    private void checkRisk767(YtDyzUser dyzUser, List<YtDyzAdRecord> adRecords, YtPlatformUserApp userApp) {
        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-767");
        if (riskTempView == null || riskTempView.getEnabled() != 1) return;
        Map<String, String> riskConfigMap = getRsihConfigMap(riskTempView);
        int monthCount = Integer.parseInt(riskConfigMap.getOrDefault("monthCount", "0"));
        int monthTaskLimit = Integer.parseInt(riskConfigMap.getOrDefault("monthTaskLimit", "0"));
        if (monthCount > 0 && monthTaskLimit > 0) {
            Date limitStartDate = DateUtil.offset(new Date(), DateField.MONTH, -monthCount);
            int recordCount = 0;
            try {
                Object o = feignInvoker.invoke(userApp.getServiceName(), "queryRecordMonthCount",
                        dyzUser.getUserId(), AdSourceTypeEnum.rewarded_video.getAdSourceType(),
                        DateUtil.format(limitStartDate, "yyyy-MM-dd HH:mm:ss"));
                JSONObject object = JSON.parseObject(JSON.toJSONString(o));
                if (object.get("data") != null) {
                    recordCount = Integer.parseInt(object.get("data").toString());
                }
            } catch (Exception e) {
                log.warn(StrUtil.format("下游服务[{}]未实现接口 queryRecordMonthCount，使用原有逻辑", userApp.getServiceName()));
                List<YtDyzAdRecord> revenues = adRecords.stream().filter(
                                s -> Objects.nonNull(s.getRevenue()) && (s.getRevenue().compareTo(BigDecimal.ZERO)) > 0)
                        .filter(item -> DateUtil.parse(item.getFinishTime()).after(limitStartDate))
                        .collect(Collectors.toList());
                recordCount = revenues.size();
            }
            if (recordCount > monthTaskLimit) {
                log.warn(StrUtil.format("[risk 767]appId:{} userId:{} adRecordCount:{} monthTaskLimit:{}",
                        dyzUser.getAppId(),dyzUser.getUserId(), recordCount, monthTaskLimit));
                String format = StrUtil.emptyToDefault(dyzUser.getPowerLimitTip(), "当前设备完成任务次数过多，请{}后重试！");
                throw new CommonException(StrUtil.format(format, StrUtil.format("{}月", monthCount)));
            }
        }
    }

    private Map<String, String> getRsihConfigMap(RiskTemplateView riskTempView) {
        Map<String, String> riskTempConfig = new HashMap<>();
        if (riskTempView != null && riskTempView.getEnabled() == 1) {
            riskTempConfig = riskTempView.getConfigList().stream().collect(Collectors.toMap(
                    RiskConfigView::getFieldName, RiskConfigView::getConfigVal,
                    (o1, o2) -> o2));
        }
        return riskTempConfig;
    }

    /**
     * 校验用户自定义风控规则
     */
    private void checkCustomRisk(YtDyzUser dyzUser, Integer node, List<YtDyzAdRecord> data) {
        List<RiskTemplateView> templateList = configMapper.getCustomTempConfig(
                dyzUser.getAppId(), node);
        if(CollUtil.isNotEmpty(templateList)){
            //校验用户的其他规则配置
            for (RiskTemplateView templateView : templateList) {
                if(EffectNodeEnum.LOGIN.getNode().equals(node)){
                    checkCondition(dyzUser, templateView);
                }else{
                    for (YtDyzAdRecord datum : data) {
                        checkCondition(datum, templateView);
                    }
                }
            }
        }
    }

    /**
     * 校验默认风控规则766
     * 当日用户获得{}个获得奖励的激励视频，近期前{}个获得奖励的激励视频平均收益<{}
     */
    private void checkRisk766(YtDyzUser dyzUser, List<YtDyzAdRecord> adRecords) {
        RiskTemplateView riskTempView = configMapper.getByCode(dyzUser.getAppId() + "-766");
        if (riskTempView == null || riskTempView.getEnabled() != 1) return;
        Map<String, String> rsihConfigMap = getRsihConfigMap(riskTempView);
        int adCompletedCount = Integer.parseInt(rsihConfigMap.getOrDefault("adCompletedCount", "-1"));
        int firstCount = Integer.parseInt(rsihConfigMap.getOrDefault("firstCount", "0"));
        BigDecimal averageRevenueLimit = new BigDecimal(rsihConfigMap.getOrDefault("averageRevenue", "-1"));
        if(adCompletedCount < 0 || firstCount < 0|| averageRevenueLimit.compareTo(BigDecimal.ZERO) < 0){
            return;
        }
        // 查询前firstCount条记录 收益总值，并计算平均值
        BigDecimal averageRevenue = BigDecimal.ZERO;
        if (adRecords.size() >= adCompletedCount) {
            long count = adRecords.stream()
                    .sorted(Comparator.comparing(YtDyzAdRecord::getFinishTime, Comparator.reverseOrder()))
                    .map(YtDyzAdRecord::getRevenue)
                    .filter(revenue -> revenue != null && revenue.compareTo(BigDecimal.ZERO) > 0)
                    .limit(firstCount).count();
            if(count > 0){
                averageRevenue = adRecords.stream()
                        .sorted(Comparator.comparing(YtDyzAdRecord::getFinishTime, Comparator.reverseOrder()))
                        .map(YtDyzAdRecord::getRevenue)
                        .filter(revenue -> revenue != null && revenue.compareTo(BigDecimal.ZERO) > 0)
                        .limit(firstCount).reduce(BigDecimal.ZERO, BigDecimal::add)
                        .divide(new BigDecimal(count), RoundingMode.HALF_UP);
            }
            if (averageRevenue.compareTo(averageRevenueLimit) < 0) {
                log.warn(StrUtil.format("[risk 766]appId:{} userId:{} averageRevenue{}  averageRevenueLimit {}",
                        dyzUser.getAppId(), dyzUser.getUserId(), averageRevenue, averageRevenueLimit));
                // 自定义响应
                throw new CommonException("766");
            }
        }
    }

    /**
     * 校验默认风控规则746
     */
    private void checkRisk746(YtDyzUser dyzUser, List<YtDyzAdRecord> records) {
        RiskTemplateView revenue = configMapper.getByCode(dyzUser.getAppId()+"-746");
        if (revenue == null || revenue.getEnabled() != 1) return;
        Map<String, String> revenueMap = revenue.getConfigList().stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        List<YtDyzAdRecord> revenues = records.stream().filter(
                s -> Objects.nonNull(s.getRevenue())&&(s.getRevenue().compareTo(BigDecimal.ZERO)) > 0).collect(Collectors.toList());
        int incomeCount = 0;
        incomeCount = revenues.isEmpty()?incomeCount:records.size();
        //判断当日获得收益的广告达到预设数值，触发风控规则
        int rewardCount = Integer.parseInt(revenueMap.get("rewardCount"));
        if(incomeCount>=rewardCount){
            BigDecimal income = new BigDecimal(revenueMap.get("income"));
            //获取最先的两条
            BigDecimal reduce = revenues.stream().map(YtDyzAdRecord::getRevenue).reduce(BigDecimal.ZERO, BigDecimal::add);
            if(reduce.compareTo(income)<0){
                log.warn(StrUtil.format("[risk 746]appId:{} userId:{} reduce:{}  income:{}",
                        dyzUser.getAppId(), dyzUser.getUserId(), reduce, income));
                riskLockUser(dyzUser, "746","系统判定小于最低收益限制",getTipsMsg());
            }
        }
    }

    /**
     * 校验默认风控规则742
     */
    private void checkRisk742(YtDyzUser dyzUser,List<YtDyzAdRecord> records) {
        RiskTemplateView ecpmLimit= configMapper.getByCode(dyzUser.getAppId()+"-742");
        if(ecpmLimit == null || ecpmLimit.getEnabled()!=1)return;
        Map<String, String> limitMap = ecpmLimit.getConfigList().stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        int adCount = 0;
        adCount = records.isEmpty()?adCount:records.size();
        ++adCount;
        //判断当日观看视频数已经达到风控条件预设的视频数
        int firstAdCount = Integer.parseInt(limitMap.get("firstAdCount"));
        if(adCount>=firstAdCount){
            //过滤出ecpm值小于预设值的视频数
            BigDecimal ecpm = new BigDecimal(limitMap.get("ecpm"));
            long count = records.stream().filter(
                    s -> Objects.nonNull(s.getEcpm())&&(s.getEcpm().compareTo(ecpm)) < 0).count();
            int exact = Math.toIntExact(count);
            //判断小于预设值的视频数超出预设次数
            int haveCount = Integer.parseInt(limitMap.get("haveCount"));
            if(exact>haveCount){
                log.warn(StrUtil.format("[risk 742]appId:{} userId:{} exact:{} haveCount:{}",
                        dyzUser.getAppId(), dyzUser.getUserId(), exact, haveCount));
                riskLockUser(dyzUser,"742","系统判定激励视频ecpm值过低",getTipsMsg());
            }
        }
    }

    /**
     * 风控锁定用户
     */
    private void riskLockUser(YtDyzUser dyzUser,String riskCode, String tempName, String errMsg) {
        riskLockUser(dyzUser, riskCode, tempName, errMsg, 24);
    }

    private void riskLockUser(YtDyzUser dyzUser, String riskCode, String tempName, String errMsg, int bannedLimit) {
        //增加用户风控记录
        RiskBannedParam param = new RiskBannedParam();
        param.setUserId(dyzUser.getUserId());
        param.setAppId(dyzUser.getAppId());
        param.setDitChId(dyzUser.getDitchId());
        param.setBannedLimit(bannedLimit);
        param.setOperator("系统风控");
        param.setBannedReason(tempName);
        param.setOperatorName("系统风控");
        param.setAgentId(configMapper.getApplicationOwner(dyzUser.getAppId()));
        param.setRiskCode(riskCode);
        addBannedRecord(Collections.singletonList(dyzUser.getUserId()), param);
        YtApp app = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp appInfo = appMapper.selectParentApp(app.getSuperiorId());
        scheduledExecutorService.schedule(() -> {
            YtDyzUser next = new YtDyzUser();
            next.setUserId(dyzUser.getUserId());
            next.setUserStatus(UserStatusEnum.LOCK.getCode());
            next.setRiskCode(riskCode);
            next.setRiskReason(tempName);
            feignInvoker.invoke(appInfo.getServiceName(), "updateUserInfo", next);
        }, 300, TimeUnit.MILLISECONDS);
        redisService.setTimeOutHoursStr("unlock_" + dyzUser.getUserId(), dyzUser.getAppId(), bannedLimit);
        throw new CustomerException(errMsg);
    }

    /**
     * 修改用户信息
     */
    private void modifyAppUserInfo(String userId, RiskUserStatusParam param) {
        YtDyzUser appUser = new YtDyzUser();
        appUser.setUserId(param.getUserId());
        appUser.setUserStatus(param.getUserStatus());
        switch (param.getUserStatus()){
            case 2:
                appUser.setRiskReason(param.getReason());
                //1s锁定用户，并设置 24小时后进行解锁
                redisService.setTimeOutStr("lock_"+userId,param.getAppId(),1000);
                redisService.setTimeOutHoursStr("unlock_"+userId,param.getAppId(),24);
                break;
            case 3:
                //增加封禁记录
                RiskBannedParam bannedParam = new RiskBannedParam();
                BeanUtil.copyProperties(param, bannedParam);
                bannedParam.setUserId(param.getUserId());
                bannedParam.setBannedReason(param.getReason());
                appUser.setRiskReason(param.getReason());
                addBannedRecord(Collections.singletonList(userId),bannedParam);
                //1s锁定用户，并设置 24小时后进行解锁
                redisService.setTimeOutStr("lock_"+userId,param.getAppId(),1000);
                redisService.setTimeOutHoursStr("unlock_"+userId,param.getAppId(),param.getBannedLimit()*24L);
                break;
            default: //解禁
                YtPlatformBanned banned = riskManageMapper.getLastBanned(userId);
                if(Objects.nonNull(banned)){
                    addDeblockingRecord(banned,param);
                }
                redisService.setTimeOutStr("unlock_"+userId,param.getAppId(),1000);
        }
    }

    /**
     * 增加解禁记录
     */
    private void addDeblockingRecord(YtPlatformBanned banned, RiskUserStatusParam param) {
        YtPlatformDeblocking deblocking = new YtPlatformDeblocking();
        deblocking.setDeblockingId(IdUtil.fastSimpleUUID());
        deblocking.setUserId(banned.getUserId());
        deblocking.setAppId(banned.getAppId());
        deblocking.setAgentId(param.getAgentId());
        deblocking.setOperator(param.getOperator());
        deblocking.setOperatorName(param.getOperatorName());
        deblocking.setBannedId(banned.getBannedId());
        deblocking.setDeblockingReason(param.getReason());
        deblocking.setDeblockingTime(new Date());
        riskManageMapper.addDeblockingRecord(deblocking);
    }

    /**
     * 增加封禁记录
     */
    private void addBannedRecord(List<String> userIdList,RiskBannedParam param) {
        YtPlatformBanned banned;
        for (String userId : userIdList) {
            banned = new YtPlatformBanned();
            banned.setBannedId(IdUtil.fastSimpleUUID());
            banned.setUserId(userId);
            banned.setAppId(param.getAppId());
            banned.setAgentId(param.getAgentId());
            if(Objects.nonNull(param.getDitChId())){
                banned.setChannelId(param.getDitChId().toString());
            }
            banned.setBannedLimit(Objects.isNull(param.getBannedLimit())?defaultLimit:param.getBannedLimit());
            banned.setBannedReason(StrUtil.isBlank(param.getBannedReason())?defaultReason:param.getBannedReason());
            banned.setBannedType(BannedTypeEnum.CHANNEL.getCode());
            banned.setBannedTime(new Date());
            banned.setOperator(param.getOperator());
            banned.setOperatorName(param.getOperatorName());
            banned.setRiskCode(param.getRiskCode());
            riskUserMapper.addBannedRecord(banned);
        }
    }

    /**
     * 校验风控规则
     */
    private void checkCondition(Object obj,RiskTemplateView data) {
        List<RiskConfigView> configList = data.getConfigList();
        for (RiskConfigView configView : configList) {
            //校验配置类型对应字段 这里传入的Obj 就是风控粒度对象
            checkConfig(obj,configView);
        }
    }

    /**
     * 逐个校验配置  当前仅有好运答题王的用户
     */
    private void checkConfig(Object obj, RiskConfigView vo) {
        Field[] fields = obj.getClass().getDeclaredFields();
        List<Field> cFields = Arrays.stream(fields).filter(
                s -> s.getName().equals(vo.getFieldName())).collect(Collectors.toList());
        if(CollUtil.isEmpty(cFields)){
            throw new CustomerException("未匹配到准入条件字段请联系管理员！");
        }
        for (Field conditionField : cFields) {
            conditionField.setAccessible(true);
            try{
                //多选校验值包含 单选校验值相等
                Object o = conditionField.get(obj);
                switch (vo.getMulty()){
                    case 1:
                        checkContainsVal(o,vo.getConfigVal(), vo.getFieldDesc());
                        break;
                    case 2:
                        checkSameVal(o, vo.getConfigVal(), vo.getFieldDesc());
                        break;
                    default:
                        checkCompareVal(o, vo.getConfigVal(),vo.getFieldDesc(),vo.getMulty());
                }
            }catch (Exception e){
                throw new CustomerException(e.getMessage());
            }
        }

    }

    /**
     * 校验值大于
     * @param o 校验对象值
     * @param configVal 配置值
     * @param fieldDesc 字段描述
     * @param type 3- o > configVal  4- o < configVal
     */
    private void checkCompareVal(Object o, String configVal, String fieldDesc, int type) {
        BigDecimal a = new BigDecimal(o.toString());
        BigDecimal b = new BigDecimal(configVal);
        if(type == 3 && a.compareTo(b) < 1){
                throw new CustomerException(fieldDesc+"不符合准入条件！");
        }
        if(type == 4 && b.compareTo(a) < 1){
                throw new CustomerException(fieldDesc+"不符合准入条件！");
        }
    }

    /**
     * 校验值包含
     */
    private void checkContainsVal(Object o, String configVal, String fieldDesc) {
        try{
            String[] split = configVal.split(",");
            if(o instanceof Date){
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date begin = format.parse(split[0]);
                Date end = format.parse(split[1]);
                Date date = (Date) o;
                if(!DateUtil.isIn(date, begin, end)){
                    throw new CustomerException(fieldDesc+"不符合准入条件！");
                }
            }else{
                if(!Arrays.asList(split).contains(o.toString())){
                    throw new CustomerException(fieldDesc+"不符合准入条件！");
                }
            }
        }catch (Exception e){
            throw new CustomerException(e.getMessage());
        }
    }

    /**
     * 校验值相同
     */
    private void checkSameVal(Object o, String configVal,String desc) {
        if(o instanceof Integer && Integer.parseInt(configVal) != (Integer) o){
            throw new CustomerException(desc+"不符合准入条件！");
        }
        if(o instanceof String && !configVal.equals(String.valueOf(o))){
            throw new CustomerException(desc+"不符合准入条件！");
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            if(o instanceof Date && !format.parse(configVal).equals(o)){
                throw new CustomerException(desc+"不符合准入条件！");
            }
        } catch (ParseException e) {
            throw new CustomerException(e.getMessage());
        }
    }

    @Override
    public List<YtPlatformBanned> queryBannedRecord(Date startTime, Date endTime,List<String> appIdList) {
        return riskManageMapper.queryBannedRecord(startTime, endTime,appIdList);
    }

    /**
     * 分组查询风控
     */
    @Override
    public List<YtPlatformBanned> queryBannedRecordGroupByUserId(Date startTime, Date endTime,List<String> appIdList) {
        return riskManageMapper.queryBannedRecordGroupByUserId(startTime, endTime,appIdList);
    }

    /**
     * 批量审核用户
     */
    @Override
    public void batchAudit(List<YtDyzUser> userList) {
        //查询这些用户当天的激励视频收益是否符合

    }

    /**
     * 增加解禁账号
     */
    @Override
    public synchronized void addDeblockingRecord(String userId) {
        YtPlatformBanned lastBanned = riskManageMapper.getLastBanned(userId);
        if(Objects.isNull(lastBanned))return;
        //查询封禁是否有解，有解则无需再解
        List<RiskDeblockingListView> deblockByBanned =
                riskManageMapper.getDeblockByBanned(lastBanned.getBannedId(), lastBanned.getUserId(), lastBanned.getBannedTime());
        if(CollUtil.isNotEmpty(deblockByBanned))return;

        YtPlatformDeblocking deblocking = new YtPlatformDeblocking();
        deblocking.setDeblockingId(IdUtil.fastSimpleUUID());
        deblocking.setAgentId(lastBanned.getAgentId());
        deblocking.setBannedId(lastBanned.getBannedId());
        deblocking.setAppId(lastBanned.getAppId());
        deblocking.setChannelId(lastBanned.getChannelId());
        deblocking.setOperator(lastBanned.getOperator());
        deblocking.setOperatorName(lastBanned.getOperatorName());
        deblocking.setUserId(userId);
        deblocking.setDeblockingTime(new Date());
        deblocking.setDeblockingReason("到期解封");
        riskManageMapper.addDeblockingRecord(deblocking);
    }

    /**
     * 根据应用查询风控配置模版
     */
    @Override
    public List<RiskTemplateView> getRiskTemplate(String appId) {
        return configMapper.getTemplateByAppId(appId);
    }

    /**
     * 校验注册风控规则
     */
    @Override
    public Result<?> checkRegRisk(YtDyzUser dyzUser) {
        RiskTemplateView view= configMapper.getByCode(dyzUser.getAppId()+"-333");
        return checkReg333(dyzUser,view);
    }

    /**
     * 校验登录风控规则
     */
    @Override
    public Result<?> checkLoginRisk(YtDyzUser dyzUser) {
        long start = System.currentTimeMillis();
        // 20250908 移除游客登陆校验
//        // 游客登陆风控校验
//        RiskTemplateView view = configMapper.getByCode(dyzUser.getAppId() + "-323");
//        checkVisitor323(dyzUser, view);
        //查询当前用户今日有被解封则直接放行
        List<RiskDeblockingListView> vo = riskManageMapper.queryTodayDeblock(dyzUser.getUserId());
        if(CollUtil.isNotEmpty(vo)){
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        // 游客用户总收益校验
        if(dyzUser.getLoginType() != null && LoginType.VISITOR == dyzUser.getLoginType()){
            // 已通过用户 不再进行校验
            String cacheKey = StrUtil.format("visitor:login_validity_{}_{}", dyzUser.getAppId(), dyzUser.getUserId());
            if (redisService.hasKey(cacheKey)) {
                log.info("visitor[{}] last check success time < {} h", dyzUser.getUserId(), loginCheckValidityPeriod);
                log.debug(StrUtil.format("[checkLoginRisk] appId:{} (end - start):{}",
                        dyzUser.getAppId(), System.currentTimeMillis() - start));
                return Result.resultOk(RepMessage.RISK_SUCCESS);
            }
            boolean checkResult = checkVisitorRisk(dyzUser);
            if (!checkResult) {
               // 20250911 需求调整 锁定用户
                //增加用户风控记录
                RiskBannedParam param = new RiskBannedParam();
                param.setUserId(dyzUser.getUserId());
                param.setAppId(dyzUser.getAppId());
                param.setDitChId(dyzUser.getDitchId());
                param.setBannedLimit(1*24);
                param.setBannedReason("预估收益不到上限游客用户风控");
                param.setOperator("系统风控");
                param.setOperatorName("系统风控");
                if (configMapper.getApplicationOwner(dyzUser.getAppId()) == null){
                    YtPlatformUserApp userApp = appMapper.selectParentApp(dyzUser.getAppId());
                    param.setAgentId(userApp.getUserId());
                }
                param.setAgentId(configMapper.getApplicationOwner(dyzUser.getAppId()));
                addBannedRecord(Collections.singletonList(dyzUser.getUserId()),param);
                YtPlatformUserApp userApp = null;
                if (dyzUser.getIosId() != null) {
                    userApp = appMapper.selectParentApp(dyzUser.getAppId());
                }
                if (userApp == null && dyzUser.getAppId() != null) {
                    YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
                    userApp = ytApp == null ? null : appMapper.selectParentApp(ytApp.getSuperiorId());
                }
                if (userApp != null) {
                    YtPlatformUserApp finalUserApp = userApp;
                    scheduledExecutorService.schedule(()->{
                        YtDyzUser next = new YtDyzUser();
                        next.setUserId(dyzUser.getUserId());
                        next.setUserStatus(UserStatusEnum.VISITOR_LOCK.getCode());
                        next.setRiskCode("344");
                        next.setRiskReason("预估收益不到上限游客用户风控");
                        feignInvoker.invoke(finalUserApp.getServiceName(), "updateUserInfo",next);
                    },300, TimeUnit.MILLISECONDS);
                } else {
                    log.error("[checkLoginRisk] 未找到下游服务 无法回调updateUserInfo appId:{}", dyzUser.getAppId());
                }
                //修改为解锁用户存入redis 24小时后进行解锁
                log.debug(StrUtil.format("[checkLoginRisk] appId:{} (end - start):{}",
                        dyzUser.getAppId(), System.currentTimeMillis() - start));
                return Result.resultErr(RepMessage.RISK_VISITOR_LOWER_VALUE);
            }
//            return checkResult ? Result.resultOk(RepMessage.RISK_SUCCESS) : Result.resultErr(RepMessage.RISK_VISITOR_LOWER_VALUE);
            redisService.setTimeOutHoursStr(cacheKey, "ok", loginCheckValidityPeriod);
        }
        log.debug(StrUtil.format("[checkLoginRisk] appId:{} (end - start):{}",
                dyzUser.getAppId(), System.currentTimeMillis() - start));
        return Result.resultOk(RepMessage.RISK_SUCCESS);
    }

    /**
     * 校验游客登录323规则
     */
    private void checkVisitor323(YtDyzUser dyzUser, RiskTemplateView view) {
        if(view != null && view.getEnabled() != 1) return;
        List<RiskConfigView> configList = view == null ? Collections.emptyList() : view.getConfigList();
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        //判断同应用 UID生成数如果不小于配置数则触发规则校验
        int uidCount = Integer.parseInt(configMap.getOrDefault("uidCount", "0"));
        int hours = Integer.parseInt(configMap.getOrDefault("hours", "0"));
        //根据用户所属应用查询该应用母包openid查询用户信息
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp userApp =  appMapper.selectParentApp(ytApp.getSuperiorId());
        Object o = feignInvoker.invoke(userApp.getServiceName(), "getLoginDitchCount", dyzUser.getDeviceId(), hours);
        int ditchCount = (int)o;
        //三天内注册的渠道数小于预设的渠道数通过校验，否则风控锁定用户
        if(ditchCount<uidCount)return;
        riskLockUser(dyzUser,"323",hours+"小时内登录渠道已达上限",getTipsMsg());
    }


    /**
     * 344/345 游客模式登陆前三条广告收益 风控校验<br>
     * 原生（信息流）+ 开屏广告 + 横幅对应 总预估收益 小于 阈值时，校验失败
     * @param dyzUser 该设备同应用的最新一条注册用户信息
     */
    private boolean checkVisitorRisk(YtDyzUser dyzUser) {
        RiskTemplateView view = configMapper.getByCode(dyzUser.getAppId() + "-344");
        RiskTemplateView view2 = configMapper.getByCode(dyzUser.getAppId() + "-345");

        boolean checkResult = true;
        if((view != null && view.getEnabled() == 1) || (view2 != null && view2.getEnabled() == 1)) {
            //根据用户所属应用查询该应用母包openid查询用户信息
            // 查询数据
            BigDecimal totalRevenue = BigDecimal.ZERO;
            BigDecimal totalEcpm = BigDecimal.ZERO;
            if (CollUtil.isNotEmpty(dyzUser.getPreAdRecordList())) {
                for (YtDyzAdRecord adRecord : dyzUser.getPreAdRecordList()) {
                    totalRevenue = adRecord.getRevenue() == null ? totalRevenue : totalRevenue.add(adRecord.getRevenue());
                    totalEcpm = adRecord.getEcpm() == null ? totalEcpm : totalEcpm.add(adRecord.getEcpm());
                }
            }
            // 校验总预估收益
            if (view != null && view.getEnabled() == 1) {
                BigDecimal limitValue = getLimitValue(view, "revenueLimit");
                checkResult = totalRevenue.compareTo(limitValue) >= 0;
                if (!checkResult) {
                    log.warn(StrUtil.format("[risk 344]appId:{} userId:{} totalRevenue:{} revenueLimit:{}",
                            dyzUser.getAppId(), dyzUser.getUserId(), totalRevenue, limitValue));
                }
            }
            // 校验总ecpm
            if (view2 != null && view2.getEnabled() == 1) {
                BigDecimal limitValue = getLimitValue(view2, "ecpmLimit");
                checkResult = checkResult && totalEcpm.compareTo(limitValue) >= 0;
                if (!checkResult) {
                    log.warn(StrUtil.format("[risk 345]]appId:{} userId:{}, totalEcpm:{} ecpmLimit:{}",
                            dyzUser.getAppId(), dyzUser.getUserId(), totalEcpm, limitValue));
                }
            }
        }
        return checkResult;
    }

    private BigDecimal getLimitValue(RiskTemplateView view, String configCode){
        List<RiskConfigView> configList = view == null ? Collections.emptyList() : view.getConfigList();
        Map<String, String> configMap = configList.stream()
                .filter(item -> item.getMulty() != null)
                .collect(Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal,
                        (o1,o2)-> o2));
        return new BigDecimal(configMap.getOrDefault(configCode, "0"));
    }

    /**
     * 333 注册风控可配置
     *  需求要求改为小时控制
     * @param dyzUser 该设备同应用的最新一条注册用户信息
     * @param view 当前校验的设备登录的应用风控配置
     */
    private Result<?> checkReg333(YtDyzUser dyzUser, RiskTemplateView view) {
        //根据用户所属应用查询该应用母包openid查询用户信息
        List<RiskConfigView> configList = view == null ? Collections.emptyList() : view.getConfigList();
        Map<String, String> configMap = configList.stream().collect(
                Collectors.toMap(RiskConfigView::getFieldName, RiskConfigView::getConfigVal));
        //得到配置的注册数
        int uidCount = Integer.parseInt(configMap.getOrDefault("uidCount", "0"));
        //得到配置的时间间隔
        int hours = Integer.parseInt(configMap.getOrDefault("hours", "0"));

        //根据用户所属应用查询该应用母包openid查询用户信息
        YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
        YtPlatformUserApp userApp =  appMapper.selectParentApp(ytApp.getSuperiorId());
        Object o = feignInvoker.invoke(userApp.getServiceName(),"getDitchCount",dyzUser.getDeviceId(),hours);
        int regCount = (int) o;
        if(regCount<uidCount){
            return Result.resultOk(RepMessage.RISK_SUCCESS);
        }
        return Result.resultErr(hours+RepMessage.DITCH_REG_LIMIT);
    }

    @Override
    public Result<?> checkAdRisk766(YtDyzUser dyzUser) {
        long start = System.currentTimeMillis();
        YtPlatformUserApp userApp;
        if (dyzUser.getIosId()!=null) {
            userApp =  appMapper.selectParentApp(dyzUser.getAppId());
        } else {
            YtApp ytApp = appMapper.selectRiskApp(dyzUser.getAppId());
            userApp = appMapper.selectParentApp(ytApp.getSuperiorId());
        }
        Object o = feignInvoker.invoke(userApp.getServiceName(), "adRecords", dyzUser.getUserId(),
                AdSourceTypeEnum.rewarded_video.getAdSourceType());
        JSONObject object = JSON.parseObject(JSON.toJSONString(o));
        List<YtDyzAdRecord> adRecords = JSONArray.parseArray(object.getString("data"), YtDyzAdRecord.class);
        if(CollUtil.isEmpty(adRecords)){
            log.debug(StrUtil.format("[checkAdRisk766] appId:{} (end - start):{}",
                    dyzUser.getAppId(), System.currentTimeMillis() - start));
            return Result.resultOk(RepMessage.QUERY_SUCCESS);
        }
        //过滤出当日的激励视频数据 按照时间正序排以便筛选每日前几条激励视频
        adRecords = adRecords.stream().filter(s ->
                DateUtil.isSameDay(DateUtil.parse(s.getFinishTime()), new Date())
        ).sorted(Comparator.comparing(YtDyzAdRecord::getFinishTime)).collect(Collectors.toList());
        if (!adRecords.isEmpty() && adRecords.size() > 1) {
            try {
                // 校验风控766规则
                checkRisk766(dyzUser, adRecords);
            } catch (CommonException e) {
                log.debug(StrUtil.format("[checkAdRisk766] appId:{} (end - start):{}",
                        dyzUser.getAppId(), System.currentTimeMillis() - start));
                return Result.resultFail(StatusCode.ACCESS_ERR, e.getMessage());
            }
        }
        log.debug(StrUtil.format("[checkAdRisk766] appId:{} (end - start):{}",
                dyzUser.getAppId(), System.currentTimeMillis() - start));
        return Result.resultOk(RepMessage.QUERY_SUCCESS);
    }


}
