package cn.quantgroup.xyqb.aspect.captcha;

import cn.qg.ec.model.base.BusinessEventBaseInfo;
import cn.qg.ec.model.user.UserLoginRegEvent;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.enums.Device;
import cn.quantgroup.xyqb.entity.enums.KeyType;
import cn.quantgroup.xyqb.model.AuthBean;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginRefuseResult;
import cn.quantgroup.xyqb.risk.entity.CountDevice;
import cn.quantgroup.xyqb.risk.entity.LoginInfo;
import cn.quantgroup.xyqb.risk.repository.LoginInfoRepository;
import cn.quantgroup.xyqb.risk.repository.WhiteListRepository;
import cn.quantgroup.xyqb.risk.uereventcollecting.UserEventCollectingUtil;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.util.TenantUtil;
import cn.quantgroup.xyqb.util.encrypt.Md5Util;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ：dongjianhua
 * @date ：Created in 2020/11/20 14:55
 * @description：设备拦截器切面
 * @modified By：
 * @version:
 */
@Aspect
@Component
@Slf4j
@Order(10)//同心圆最外层
public class LoginInterceptorAspect {

    @Resource
    private LoginInfoRepository loginInfoRepository;

    @Resource
    private WhiteListRepository whiteListRepository;

    @Resource
    private IUserService iUserService;

    private static final String DEFAULT_CODE = "00000000-0000-0000-0000-000000000000";
    /**
     * 设备拒绝阈值
     */
    private static final Long DEVICE_REFUSE_COUNT = 3L;
    /**
     * 风控拒绝策略1设备维度 2 账号维度
     */
    private static final int[] RISK_STRATEGY = {1, 2};
    /**
     * 警示语
     */
    private final String ALERT_WORDS = "检测到您的设备上账号登录异常，已被强制退出并暂时冻结您的账号。联系客服400-002-0061";


    /**
     * 拒绝策略e
     */
    private static Map<Device, Long> DEVICE_REFUSE_STRATEGY = Maps.newHashMap();

    static {
        DEVICE_REFUSE_STRATEGY.put(Device.ANDROID, 3L);
        DEVICE_REFUSE_STRATEGY.put(Device.IOS, 7L);
    }


    @Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor)")
    public void pointCutAt() {
    }

    @Around("pointCutAt()")
    private Object around(ProceedingJoinPoint pjp) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        byte[] bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());

        String body = new String(bodyBytes, request.getCharacterEncoding());
        Integer tenantId = null;
        if (body != null) {
            HashMap hashMap = JSON.parseObject(body, HashMap.class);
            tenantId = Integer.parseInt(hashMap.get("tenantId").toString());
        }
        /**
         * 拿参数
         */
        String idfa = request.getHeader("idfa");
        String scDeviceId = request.getHeader("scDeviceId");
        String realIp = request.getHeader("x-real-ip");
        String deviceCode = request.getHeader("x-user-platform");
        String phone = request.getHeader("x-user-phone");
        String deviceId = null;

        if (null != scDeviceId && !"".equals(scDeviceId)) {
            deviceId = scDeviceId;
        } else if (null != idfa && !"".equals(idfa)) {
            deviceId = idfa;
        } else {
            /**
             * 没有获取到就用默认的
             */
            deviceId = DEFAULT_CODE;
        }

        log.info("登录前风控策略开始deviceId:{},phone:{}", deviceId, phone);
        Device device = Device.valueOfCode(deviceCode);
        /**
         * 尝试登录发一下
         */
        sendEvent(UserLoginRegEvent.SubEventType.user_attempt_login, device, phone, deviceId, realIp, null, tenantId);

        LoginRefuseResult result = verification(scDeviceId, phone, device);

        Object loginResult = null;
        try {
            if (!result.isPass()) {
                log.warn("登录命中风控策略deviceId:{},phone:{},realIp:{},deviceCode:{}", deviceId, phone, realIp, deviceCode);
                return JsonResult.buildErrorStateResult(ALERT_WORDS, null);
            }

            loginResult = pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            /**
             * 保存登录信息
             */
            saveLoginInfo(phone, device, deviceId, realIp, result, loginResult, tenantId);
        }
        return loginResult;
    }

    /**
     * @param deviceId
     * @param phone
     * @return
     */
    private LoginRefuseResult verification(String deviceId, String phone, Device device) {
        try {
            /**
             * 默认code不需要
             */
            if (DEFAULT_CODE.equals(deviceId)) {
                return LoginRefuseResult.builder()
                        .isPass(Boolean.TRUE).build();
            }
            /**
             * 如果没传手机也放过吧
             */
            if (null == phone || "".equals(phone)) {
                log.warn("怎么没传手机号进来啊暂时放过吧");
                return LoginRefuseResult.builder()
                        .isPass(Boolean.TRUE).build();
            }
            /**
             * 阈值
             */
            Long threshold = DEVICE_REFUSE_STRATEGY.get(device);

            if (null == threshold) {
                log.warn("非安卓或者IOS设备登录没有策略就放过phone:{},device:{}", phone, device);
                return LoginRefuseResult.builder()
                        .isPass(Boolean.TRUE).build();
            }

            Long deviceNum = loginInfoRepository.countByDeviceId(deviceId, phone);

            if (DEVICE_REFUSE_COUNT.compareTo(deviceNum) <= 0) {
                if (!isWhite(deviceId, KeyType.DEVICEID)) {
                    log.warn("此设备登录命中拒绝策略deviceId:{}超过{}个拒绝登录", deviceId, deviceNum);
                    return LoginRefuseResult.builder()
                            .isPass(Boolean.FALSE)
                            .rule(RISK_STRATEGY[0])
                            .threshold(DEVICE_REFUSE_COUNT.intValue())
                            .value(deviceNum.intValue())
                            .build();
                }

            }

            CountDevice countDevice = loginInfoRepository.countByPhoneAndDevice(phone, device.ordinal(), deviceId);

            if (null == countDevice) {
                return LoginRefuseResult.builder()
                        .isPass(Boolean.TRUE)
                        .build();
            }
            /**
             * 实际的数量
             */
            Long val = countDevice.getNum();

            if (threshold.compareTo(val) <= 0) {
                if (!isWhite(phone, KeyType.PHONE)) {
                    log.warn("此账户登录命中拒绝策略并且没有白名单phone:{},device:{}", phone, device);
                    return LoginRefuseResult.builder()
                            .isPass(Boolean.FALSE)
                            .rule(RISK_STRATEGY[1])
                            .threshold(threshold.intValue())
                            .value(Long.valueOf(val).intValue())
                            .build();
                }
                log.warn("此账户登录命中拒绝策略存在白名单phone:{}", phone);
            }
        } catch (Exception e) {
            log.error("用户登录策略校验异常了", e);
        }
        return LoginRefuseResult.builder()
                .isPass(Boolean.TRUE)
                .build();
    }

    /**
     * 保存信息
     *
     * @param phone
     * @param device
     * @param deviceId
     * @param ip
     */
    private void saveLoginInfo(String phone, Device device, String deviceId, String ip, LoginRefuseResult refuseResult, Object loginResult, Integer tenantId) {
        try {
            /**
             * 默认code不需要
             * 默认CODE 不存
             */
            if (DEFAULT_CODE.equals(deviceId)) {
                return;
            }
            if (null == phone || "".equals(phone)) {
                log.warn("没有手机号先不存了");
                return;
            }

            LoginInfo info = new LoginInfo();

            info.setPhoneNo(phone);

            info.setDeviceId(deviceId);

            info.setDevice(device);

            info.setIp(ip);

            info.setIsPass(Boolean.TRUE);

            if (!refuseResult.isPass()) {
                info.setIsPass(Boolean.FALSE);
                info.setHitRule(refuseResult.getRule());
                info.setThreshold(refuseResult.getThreshold());
                info.setValue(refuseResult.getValue());
            }
            if (null == loginResult) {
                info.setIsLogin(Boolean.FALSE);
                info.setLoginFailMsg("系统异常");
                if (!refuseResult.isPass()) {
                    info.setLoginFailMsg("命中风控策略");
                }

            } else {
                if (loginResult instanceof JsonResult) {
                    if (((JsonResult) loginResult).isSuccess()) {
                        info.setIsLogin(Boolean.TRUE);
                        sendEvent(UserLoginRegEvent.SubEventType.user_only_login, device, phone, deviceId, ip, loginResult, tenantId);
                    } else {
                        info.setIsLogin(Boolean.FALSE);
                        info.setLoginFailMsg(((JsonResult) loginResult)
                                .getMsg());
                    }
                } else {
                    log.warn("意想不到的情况按理讲不存在loginResult:{}", loginResult);
                }
            }
            loginInfoRepository.save(info);
        } catch (Exception e) {
            log.error("保存登录信息异常phone:{}", phone, e);
        }

    }

    /**
     * 是否在白名单
     *
     * @param key
     * @param type
     * @return
     */
    private boolean isWhite(String key, KeyType type) {
        return whiteListRepository.countByKeyEqualsAndTypeEqualsAndEnableIsTrue(key, type) > 0;
    }

    /**
     * 发送登录事件
     *
     * @param type
     * @param device
     * @param phone
     * @param deviceId
     * @param ip
     * @param loginResult
     */
    private void sendEvent(UserLoginRegEvent.SubEventType type, Device device, String phone, String deviceId, String ip, Object loginResult, Integer tenantId) {

        try {
            if (null == phone) {
                log.info("手机号没有直接估计是切面用在了不能用的地方直接放过");
                return;
            }
            log.info("开始发送登录注册消息phone:{}", phone);
            UserLoginRegEvent.UserLoginRegEventBuilder builder = UserLoginRegEvent.builder();

            builder.maskPhoneNo(phone);
            builder.hashPhoneNo(Md5Util.build(Md5Util.build(phone)));
            builder.subEventType(type);

            BusinessEventBaseInfo.BusinessEventBaseInfoBuilder baseInfoBuilder
                    = BusinessEventBaseInfo.builder();
            if (null != loginResult) {
                if (loginResult instanceof JsonResult) {
                    JsonResult jsonResult = ((JsonResult) loginResult);
                    if (jsonResult.isSuccess()) {
                        Object data = jsonResult.getData();
                        if (data instanceof AuthBean) {

                            String uuid = ((AuthBean) data).getUuid();
                            User user = iUserService.findByUuidWithCache(uuid);
                            baseInfoBuilder.userUuid(uuid);
                            baseInfoBuilder.channel(String.valueOf(user.getRegisteredFrom()));
                        } else {
                            return;
                        }

                    } else {
                        return;
                    }
                }
            }

            baseInfoBuilder.deviceId(deviceId);
            baseInfoBuilder.ip(ip);
            baseInfoBuilder.deviceType(device.getCode());
            builder.businessEventBaseInfo(baseInfoBuilder.build());

            if (tenantId == null || tenantId.equals(TenantUtil.TENANT_DEFAULT)) {
                UserEventCollectingUtil.addEvent(builder.build());
            }
        } catch (Exception e) {
            log.error("发送用户登录事件异常phone:{}", phone);
        }

    }
    /**
     * 【登录设备反欺诈策略】
     *
     * 一、拿设备唯一码
     * 1.安卓： 只拿神策$device_id 缺失不做处理
     * 2.IOS： 先拿神策$device_id 缺失则再拿IDFA 最后还是缺失 和 00000000-0000-0000-0000-000000000000 不做处理
     *
     * 二、设备维度策略
     * 1.触发条件：  当该设备在90天内 累计尝试登录超过3个uuid账号
     * 2.设备措施： 该设备冻结加入黑名单 该设备上任何uuid账号都不能登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单； （预计涉及万3的安卓设备，万5的IOS设备；包含了本次山东赵某的设备）
     *
     * 三、账号维度策略
     * 1.触发条件： 当该uuid账号在90天内 在超过（安卓：3，IOS：7）个设备尝试过登录时 （目前同一IOS的设备唯一码可能会变，重复率约是安卓的1倍多）
     * 2.账号措施：该uuid帐号冻结加入黑名单 该uuid帐号不能在任何设备登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单 （预计涉及万6的安卓用户，万5的IOS用户）
     */
}
