package cn.superdesk.uniorder.common.security.sensitive;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.superdesk.uniorder.common.security.sensitive.annotion.Desensitized;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Description: 敏感数据脱敏处理 - 工具类
 * @Author: linjinyu
 * @CreateDate: 2021/11/25 上午9:31
 * @UpdateUser: linjinyu
 * @UpdateDate: 2021/11/25 上午9:31
 * @UpdateRemark: 修改内容
 * @Version: 1.0
 */
@Slf4j
public class DesensitizedUtils {

	/**
	 * 递归深度计数器:防止循环依赖
	 */
	private static ThreadLocal<AtomicInteger> threadLocalCounter = new ThreadLocal<>();

	/**
	 * 递归的最大深度
	 */
	private static final Integer MAX_DEPTH = 3;

	/**
	 * 敏感数据脱敏
	 * 脱敏对象 的字段 需要添加 {@Sensitive} 注解并标注敏感类型
	 *
	 * @param bean
	 * @return
	 */
	public static Object desensitization(Object bean) {
		try {

			// 控制递归的深度:防止循环依赖
			threadLocalCounter.set(new AtomicInteger(MAX_DEPTH));

			doDesensitization(bean);

		} catch (Exception e) {
			log.warn("数据脱敏工具异常", e);
		} finally {
			threadLocalCounter.remove();
		}

		return bean;
	}

	/**
	 * 遍历递归脱敏操作
	 *
	 * @param bean
	 * @throws Exception
	 */
	private static void doDesensitization(Object bean) throws Exception {

		if (Objects.isNull(bean) || isBeyoudMaxDepth(threadLocalCounter.get())) {
			return;
		}

		// 处理子属性,包括集合中的
		Object object = null;

		// 对数组类型的字段进行递归过滤
		if (bean.getClass().isArray()) {

			for (int i = 0; i < Array.getLength(bean); i++) {
				object = Array.get(bean, i);
				if (isNotGeneralType(object.getClass())) {
					desensitizationField(object);
				}
			}
		}
		// 对集合类型的字段进行递归过滤
		else if (bean instanceof Collection<?>) {

			Iterator<?> it = ((Collection<?>) bean).iterator();
			while (it.hasNext()) {
				object = it.next();
				if (isNotGeneralType(object.getClass())) {
					desensitizationField(object);
				}
			}
		}
		// 对Map类型的字段进行递归过滤
		else if (bean instanceof Map<?, ?>) {

			Map<?, ?> map = (Map<?, ?>) bean;
			for (Object o : map.entrySet()) {
				object = ((Map.Entry<?, ?>) o).getValue();
				if (isNotGeneralType(object.getClass())) {
					desensitizationField(object);
				}
			}
		}
		// 自定义类等
		else {
			if (isNotGeneralType(bean.getClass())) {
				desensitizationField(bean);
			}
		}
	}

	/**
	 * 脱敏bean所有属性
	 *
	 * @param bean
	 * @throws Exception
	 */
	private static void desensitizationField(Object bean) throws Exception {

		// 获取对象的所有属性
		List<Field> fields = getAllFields(bean);
		if (CollUtil.isEmpty(fields)) {
			return;
		}

		Object value;
		for (Field field : fields) {

			field.setAccessible(true);
			value = field.get(bean);

			// 属性是final类型  不处理
			if (Objects.isNull(value)) {
				continue;
			}

			// 不是基本数据类型
			if (isNotGeneralType(value.getClass())) {
				doDesensitization(value);
			} else {
				// 脱敏操作
				setNewValueForField(bean, field, value);
			}
		}
	}

	/**
	 * 获取包括父类所有 使用了{@Desensitized}注解的属性
	 *
	 * @param objectSource
	 * @return
	 */
	private static List<Field> getAllFields(Object objectSource) {

		if (Objects.isNull(objectSource)) {
			return null;
		}

		AtomicInteger atomicInteger = new AtomicInteger(MAX_DEPTH);

		// 获得当前类的所有属性(private、protected、public)
		List<Field> fieldList = Lists.newArrayList();
		Class       tempClass = objectSource.getClass();

		while (Objects.nonNull(tempClass) && !tempClass.getName().toLowerCase().equals("java.lang.object") && !isBeyoudMaxDepth(atomicInteger)) {
			fieldList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
			//得到父类,然后赋给自己
			tempClass = tempClass.getSuperclass();
		}

		// 过滤出来需要
		return fieldList.stream().filter(field -> !Modifier.isFinal(field.getModifiers()) && Objects.nonNull(field.getAnnotation(Desensitized.class))).collect(Collectors.toList());
	}

	/**
	 * 排除基础类型、枚举类型、扩展库类型的字段
	 *
	 * @param clazz
	 * @return
	 */
	private static boolean isNotGeneralType(Class<?> clazz) {

		return !clazz.isPrimitive()
				&& !clazz.isEnum()
				&& Objects.nonNull(clazz.getPackage())
				&& !StrUtil.startWith(clazz.getPackage().getName(), "javax.")
				&& !StrUtil.startWith(clazz.getPackage().getName(), "java.lang")
				&& !StrUtil.startWith(clazz.getPackage().getName(), "java.time");
	}

	/**
	 * 脱敏操作(按照规则转化需要脱敏的字段并设置新值)
	 * 目前只支持String类型的字段
	 *
	 * @param bean
	 * @param field
	 * @param value
	 * @throws IllegalAccessException
	 */
	public static void setNewValueForField(Object bean, Field field, Object value) {

		try {
			// 处理自身的属性
			Desensitized annotation = field.getAnnotation(Desensitized.class);

			String valueStr;
			if (!field.getType().equals(String.class) || Objects.isNull(value)
					|| StrUtil.isEmpty(valueStr = value.toString()) || Objects.isNull(annotation)) {
				return;
			}

			switch (annotation.type()) {

				case CHINESE_NAME_MODE_1: {
					field.set(bean, chineseNameMode1(valueStr));
					break;
				}
				case CHINESE_NAME_MODE_2: {
					field.set(bean, chineseNameMode2(valueStr));
					break;
				}
				case CHINESE_NAME_MODE_3: {
					field.set(bean, chineseNameMode3(valueStr));
					break;
				}
				case ID_CARD: {
					field.set(bean, idCardNum(valueStr));
					break;
				}
				case MOBILE_PHONE: {
					field.set(bean, mobilePhone(valueStr));
					break;
				}
				case ADDRESS_MODE_1: {
					field.set(bean, addressMode1(valueStr, 7));
					break;
				}
				case ADDRESS_MODE_2: {
					field.set(bean, addressMode2(valueStr));
					break;
				}
				case EMAIL: {
					field.set(bean, email(valueStr));
					break;
				}
				case BANK_CARD: {
					field.set(bean, bankCard(valueStr));
					break;
				}
				default:
					break;
			}
		} catch (Exception e) {
			log.warn("数据脱敏工具异常,bean:{},field:{},value:{}", JSONUtil.toJsonStr(bean), field.getName(), value, e);
		}
	}

	private static Boolean isBeyoudMaxDepth(AtomicInteger atomicInteger) {

		if (Objects.isNull(atomicInteger)) {
			return Boolean.FALSE;
		}

		final int increment = atomicInteger.getAndDecrement();

		if (increment <= 0) {
			return Boolean.TRUE;
		}

		return Boolean.FALSE;
	}


	/****************************************** AnStringSensitiveInfoUtils start ******************************************/

	/****************************************** org.apache.commons.lang3.StringUtils start ******************************************/

	/**
	 * [银行卡号] 数据脱敏
	 * 只留前四位和后四位
	 * 4420 0383 3938 3938 167 脱敏结果: 4420 **** **** ***8 167
	 *
	 * @param value
	 * @return
	 */
	public static String bankCard(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		return StringUtils.left(value, 4).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
	}

	/**
	 * [邮箱] 数据脱敏
	 * 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示
	 * 例子:goub**@gmail.com
	 *
	 * @param value
	 * @return
	 */
	public static String email(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		int index = StringUtils.indexOf(value, "@");
		if (index <= 1) {
			return value;
		} else {
			return StringUtils.rightPad(StringUtils.left(value, 1), index, "*").concat(StringUtils.mid(value, index, StringUtils.length(value)));
		}
	}

	/**
	 * [身份证号] 脱敏类型
	 * 前3位,后4位
	 * 167722199102327777 脱敏后: 167*************7777
	 *
	 * @param value
	 * @return
	 */
	public static String idCardNum(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
	}

	/**
	 * [手机号码] 数据脱敏
	 * 16967677777 脱敏后: 169****7777
	 *
	 * @param value
	 * @return
	 */
	public static String mobilePhone(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		return StringUtils.left(value, 3).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(value, 4), StringUtils.length(value), "*"), "***"));
	}

	/**
	 * [姓名] 真实姓名脱敏 只留第一个字
	 * 中文姓名只显示最后一个汉字,其他隐藏为星号
	 * 大张雪 -> 大**
	 *
	 * @param value
	 * @return
	 */
	public static String chineseNameMode1(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		return StringUtils.rightPad(StringUtils.left(value, 1), StringUtils.length(value), "*");
	}

	/**
	 * [姓名] 真实姓名脱敏 只留最后一个字
	 * 中文姓名只显示最后一个汉字,其他隐藏为星号
	 * 大张雪 -> **雪
	 *
	 * @param value
	 * @return
	 */
	public static String chineseNameMode2(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

		return StringUtils.leftPad(StringUtils.right(value, 1), StringUtils.length(value), "*");
	}

	/**
	 * [姓名] 真实姓名脱敏 隐第一个字
	 * 中文姓名只显示最后一个汉字,其他隐藏为星号
	 * 大张雪 -> *张雪
	 *
	 * @param value
	 * @return
	 */
	public static String chineseNameMode3(String value) {

		if (StringUtils.isEmpty(value)) {
			return null;
		}

//		log.info("\n{}\n{}\n{}", value, StringUtils.right(value, StringUtils.length(value) - 1), StringUtils.leftPad(StringUtils.right(value, StringUtils.length(value) - 1), 1, "*"));

		return StringUtils.leftPad(StringUtils.right(value, StringUtils.length(value) - 1), StringUtils.length(value), "*");
	}

	/**
	 * [地址] 只显示到地区,不显示详细地址,比如:广东 深圳市 宝安区 新安街道 宝兴路6号海*******
	 *
	 * @param address
	 * @param sensitiveSize 敏感信息长度
	 * @return
	 */
	public static String addressMode1(String address, int sensitiveSize) {

		if (StringUtils.isBlank(address) || address.length() <= sensitiveSize) {
			return address;
		}

		int length = StringUtils.length(address);
		return StringUtils.rightPad(StringUtils.left(address, length - sensitiveSize), length, "*");
	}

	/**
	 * [地址] 只显示到地区,不显示详细地址,比如:广东 深圳市 宝安区 **** 宝兴路6号海xxxxxx
	 *
	 * @param address
	 * @return
	 */
	public static String addressMode2(String address) {

		if (StringUtils.isBlank(address)) {
			return address;
		}

		int length  = StringUtils.length(address);
		int portion = length / 3;
		int hidden  = length - portion;

		return StringUtils.rightPad(StringUtils.left(address, portion), hidden, "*").concat(StringUtils.right(address, portion));

	}

	/****************************************** org.apache.commons.lang3.StringUtils start ******************************************/
}
