package cn.quantgroup.xyqb.service.user.impl;


import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.exception.PasswordErrorLimitException;
import cn.quantgroup.xyqb.service.user.ILockIpv4Service;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * IPV4锁机制Service实现
 *
 * @author renwc
 */
@Slf4j
@Service
public class LockIpv4ServiceImpl implements ILockIpv4Service {

    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> stringRedisTemplate;

    @Override
    public void countErrorByPhoneNo(String phoneNo) {
        // 密码错误时，给该账号添加计数器
        String key = Constants.REDIS_PASSWORD_ERROR_COUNT + phoneNo;
        if (!stringRedisTemplate.hasKey(key)) {
            log.info("添加错误计数器，key={}", key);
            stringRedisTemplate.opsForValue().set(key, String.valueOf(0), DateUtils.getSeconds(), TimeUnit.SECONDS);
        }
        // 密码错误计数
        Long errorCount = stringRedisTemplate.opsForValue().increment(key, 1L);
        //判断上锁
        lockPhone(phoneNo, errorCount);

        if (errorCount > Constants.Image_Need_Count) {
            log.info("用户名或密码不正确，phoneNo={}", phoneNo);
            throw new PasswordErrorLimitException("用户名或密码不正确");
        } else if (Objects.equals(errorCount, Constants.Image_Need_Count)) {
            log.info("请输入图形验证码，phoneNo={}", phoneNo);
            throw new PasswordErrorLimitException("请输入图形验证码");
        }
    }

    /**
     * 对白名单之外的所有公有、私有IP执行错误计数
     *
     * @param ipv4
     */
    @Override
    public void countErrorByIpv4(String ipv4) {
        if (ValidationUtil.validateIpv4(ipv4) && !IpUtil.whiteOf(ipv4)) {
            String ipv4Key = getErrorIpKey(ipv4);
            if (!stringRedisTemplate.hasKey(ipv4Key)) {
                // 计数周期1分钟
                stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), Constants.IPV4_FAILED_COUNT_MINUTES, TimeUnit.MINUTES);
            }
            Long count = stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
            log.info("Lock_ipv4: count deny ip access: ip={}, count={}", ipv4, count);
            lockErrorIpv4(ipv4, count);
        }
    }

    @Override
    public void lockErrorIpv4(String ip, long count) {
        // 每分钟计数阈值
        long counts = Constants.IPV4_LOCK_ON_FAILED_COUNTS;
        String redisCounts = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
        if (StringUtils.isNumeric(redisCounts) && Integer.valueOf(redisCounts) > 0) {
            counts = Integer.valueOf(redisCounts);
        }
        if (count < counts) {
            return;
        }
        // 锁定时长
        long minutes = Constants.IPV4_FAILED_LOCK_MINUTES;
        String redisMinutes = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
        if (StringUtils.isNumeric(redisMinutes) && Integer.valueOf(redisMinutes) > 0) {
            minutes = Integer.valueOf(redisMinutes);
        }
        String lockIpv4Key = getLockIpv4Key(ip);
        stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), minutes, TimeUnit.MINUTES);
        log.info("Lock_ipv4: locked error ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, counts, Constants.IPV4_FAILED_COUNT_MINUTES, minutes);
    }

    /**
     * 对白名单之外的所有公有、私有IP执行成功计数
     *
     * @param ipv4
     */
    @Override
    public void countSuccessByIpv4(String ipv4) {
        if (ValidationUtil.validateIpv4(ipv4) && !IpUtil.whiteOf(ipv4)) {
            String ipv4Key = getSuccessIpKey(ipv4);
            if (!stringRedisTemplate.hasKey(ipv4Key)) {
                // 计数周期1分钟
                stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), Constants.IPV4_SUCCESS_COUNT_MINUTES, TimeUnit.MINUTES);
            }
            Long count = stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
            log.info("Lock_ipv4: count success ip access: ip={}, count={}", ipv4, count);
            // 恢复规则，观察 // Todo -- 误伤业务方，暂时下线[比如网贷之家：121.40.207.133] -- lockSuccessIpv4(ipv4, count);
            lockSuccessIpv4(ipv4, count);
        }
    }

    @Override
    public void lockSuccessIpv4(String ip, long count) {
        // 每小时计数阈值
        if (count < Constants.IPV4_LOCK_ON_SUCCESS_COUNTS) {
            return;
        }
        // 锁定时长
        String lockIpv4Key = getLockIpv4Key(ip);
        stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), Constants.IPV4_SUCCESS_LOCK_MINUTES, TimeUnit.MINUTES);
        log.info("Lock_ipv4: locked success ip access:{}, success overstep {} times in {} minutes, do lock {} minutes", ip, Constants.IPV4_LOCK_ON_SUCCESS_COUNTS, Constants.IPV4_SUCCESS_COUNT_MINUTES, Constants.IPV4_SUCCESS_LOCK_MINUTES);
    }

    /**
     * 锁定手机号
     *
     * @param phone
     * @param count
     */
    @Override
    public void lockPhone(String phone, long count) {

        if (count == Constants.PASSWORD_ERROR_LOCK_COUNT) {

            String lock_key = Constants.PASSWORD_LOCK_PRE.concat(phone);
            //Constants.PASSWORD_LOCK_PRE
            boolean isLock = stringRedisTemplate.hasKey(lock_key);

            if (!isLock) {
                stringRedisTemplate.opsForValue().set(lock_key, "lock", Constants.PASSWORD_ERROR_LOCK_TIME, TimeUnit.MINUTES);
            }
            log.warn("连续登录失败超过阈值锁半个小时count:{} ,phone:{}", count, phone);
        } else if (count > Constants.PASSWORD_ERROR_LOCK_COUNT) {
            /**
             * 如果超过阈值再进来则说明过了限制时间，解锁清楚次数
             */
            unLockPhone(phone);
        }
    }

    @Override
    public void unLockPhone(String phone) {
        try {
            stringRedisTemplate.delete(Constants.PASSWORD_LOCK_PRE.concat(phone));
            String key = Constants.REDIS_PASSWORD_ERROR_COUNT + phone;
            stringRedisTemplate.delete(key);
            log.info("解锁成功phone:{}", phone);
        } catch (Exception e) {
            log.error("解锁异常phone:{}", phone);
        }

    }

    private static String getErrorIpKey(String ipv4) {
        return Constants.REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 + ipv4;
    }

    private static String getSuccessIpKey(String ipv4) {
        return Constants.REDIS_PASSWORD_SUCCESS_COUNT_FOR_IPV4 + ipv4;
    }

    private static String getLockIpv4Key(String ipv4) {
        return Constants.IPV4_LOCK + ipv4;
    }
}
