/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved.
 * <a>http://www.crunii.com</a>
 */
package com.crunii.micro.core.aspect;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.crunii.micro.common.dto.PageRsp;
import com.crunii.micro.common.dto.Result;
import com.crunii.micro.core.annotation.PropertyTranslate;
import com.crunii.micro.core.cache.TsmPropertyCache;
import com.crunii.micro.core.dto.PropertyTranslateInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author shenhs create 2022/4/23 1:29
 */
@Component
@Aspect
public class PropertyTranslateCacheAspect {

	private static final String SIMPLE_TYPE = "SIMPLE_TYPE";
	private static final String OBJECT_TYPE = "OBJECT_TYPE";

	@Autowired
	private TsmPropertyCache propertyCache;

	private static final int localRefreshInterval = 60 * 1000;

	private final long localRefreshTime = System.currentTimeMillis();

//	private long cacheLastRefreshTime = 0;

	// 预热分析过的类，名称，作为反射缓存
	private static final ConcurrentHashMap<String, List<PropertyTranslateInfo>> reflectCache = new ConcurrentHashMap();

	// 字段缓存
	private static final ConcurrentHashMap<String, Map<String, List<Field>>> fieldCache = new ConcurrentHashMap();

	// 静态值缓存- 2023-10-11取消此处的缓存，因为在TsmPropertyCache中已经实现了缓存机制
//	private static final ConcurrentHashMap<String, String> propCache = new ConcurrentHashMap<>();


	//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
	@Pointcut("@annotation(com.crunii.micro.core.annotation.InjectProperty)")
	public void aspect2() {
	}

	@AfterReturning(value = "aspect2()", returning = "returnObj")
	public Object after2(JoinPoint joinPoint, Object returnObj) throws Throwable {

		/*
		long refreshTime = propertyCache.getRefreshTime();
		if (cacheLastRefreshTime != refreshTime) {
			propCache.clear();
			cacheLastRefreshTime = refreshTime;
		}
		*/

		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		runReflect(returnObj);
		stopWatch.stop();
//		System.out.println("反射耗费运行总时长：" + stopWatch.getTotalTimeNanos() + "ns  约" + stopWatch.getTotalTimeMillis() + "ms");
		return returnObj;
	}

	public void runReflect(Object object) {
//		System.out.println("调用链路："+object.getClass().getName());
		if (object == null || ClassUtil.isSimpleValueType(object.getClass())) {
			return;
		} else {
			isResultObj(object);
			isPageRsp(object);
			isCollection(object);
			handleObjCache(object);
			handleTranslate(object);
		}
	}

	private void handleObjCache(Object object) {
		if (object instanceof Collection || object instanceof Map) {
			return;
		}
		Class<?> clazz = object.getClass();
		Field[] declaredFields = clazz.getDeclaredFields();
		List<Field> collect = Arrays.stream(declaredFields).collect(Collectors.toList());
		Map<String, List<Field>> fieldsGroup = null;
		if (fieldCache.containsKey(clazz.getName())) {
			fieldsGroup = fieldCache.get(clazz.getName());
		} else {
			fieldsGroup = analysField(collect);
			fieldCache.put(clazz.getName(), fieldsGroup);
		}

		List<Field> objectField = fieldsGroup.get(OBJECT_TYPE);
		if (!objectField.isEmpty()) {
			handleObjectField(objectField, object);
		}

		List<Field> simpleField = fieldsGroup.get(SIMPLE_TYPE);
		if (!simpleField.isEmpty() && !reflectCache.containsKey(clazz.getName())) {
			List<PropertyTranslateInfo> propertyTranslateAnno = getPropertyTranslateAnno(simpleField, clazz);
			reflectCache.put(clazz.getName(), propertyTranslateAnno);
		}

	}

	private boolean isResultObj(Object obj) {
		if (obj instanceof Result) {
			Result res = (Result) obj;
			if(ObjectUtil.isEmpty(res.getData())){
				return false;
			}
			Object data = res.getData();
			if (ClassUtil.isSimpleValueType(data.getClass())) {
				return false;
			} else {
//				System.out.println("调用链路：isResultObj true");
				runReflect(data);
			}
			return true;
		} else {
			return false;
		}
	}

	private boolean isPageRsp(Object obj) {
		if (obj instanceof PageRsp) {
			PageRsp pageRsp = (PageRsp) obj;
			List result = pageRsp.getResult();
			if (result == null || (null != result && result.isEmpty()) || ClassUtil.isSimpleValueType(result.get(0).getClass())) {
				return false;
			} else {
//				System.out.println("调用链路：isPageRsp true");
				runReflect(result);
			}
			return true;
		} else {
			return false;
		}
	}

	private boolean isCollection(Object obj) {
		if (obj instanceof Collection) {
			List l = (List) obj;
			if ((null != l && l.isEmpty()) || ClassUtil.isSimpleValueType(l.get(0).getClass())) {
				return false;
			} else {
//				System.out.println("调用链路：isCollection true");
				l.stream().forEach(d -> {
					runReflect(d);
				});
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 分析有意义的字段，排除无意义的字段
	 * 原始字段
	 *
	 * @param fieldsArr
	 */
	public Map<String, List<Field>> analysField(List<Field> fieldsArr) {

		List<Field> simpleList = new ArrayList<>(fieldsArr.size());
		List<Field> objectList = new ArrayList<>(fieldsArr.size());
		fieldsArr.stream().forEach(field -> {
			field.setAccessible(true);
			if (ClassUtil.isSimpleValueType(field.getType())) {
				simpleList.add(field);
			} else {
				objectList.add(field);
			}

		});
		Map<String, List<Field>> map = new HashMap<>();
		map.put(SIMPLE_TYPE, simpleList);
		map.put(OBJECT_TYPE, objectList);
		return map;
	}

	public void handleObjectField(List<Field> objectFields, Object obj) {
		objectFields.stream().forEach(field -> {
//			field.setAccessible(true);
			try {
				Object data = field.get(obj);
				runReflect(data);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		});
	}

	public List<PropertyTranslateInfo> getPropertyTranslateAnno(List<Field> fieldList, Class<?> clazz) {
		List<PropertyTranslateInfo> collect = fieldList.stream().filter(field -> field.getAnnotation(PropertyTranslate.class) != null).map(field -> {
			PropertyTranslate annotation = field.getAnnotation(PropertyTranslate.class);
			String valueFiledName = annotation.valueFiled();
			Field valueFiled = ClassUtil.getDeclaredField(clazz, valueFiledName);
			if (valueFiled == null) {
				valueFiled = ClassUtil.getDeclaredField(clazz.getSuperclass(), valueFiledName);
			}
			field.setAccessible(true);
			valueFiled.setAccessible(true);
			PropertyTranslateInfo build = PropertyTranslateInfo.builder().propertyCode(annotation.propertyCode())
					.valueFiled(annotation.valueFiled())
					.targetField(field).sourceField(valueFiled).build();
			return build;
		}).collect(Collectors.toList());
		return collect;
	}

	private void handleTranslate(Object object) {
		String name = object.getClass().getName();
		if (reflectCache.containsKey(name)) {
			handleTranslate(object, reflectCache.get(object.getClass().getName()));
		}
	}

	private void handleTranslate(Object obj, List<PropertyTranslateInfo> translateInfoList) {
		if(CollectionUtils.isEmpty(translateInfoList)){
			return;
		}
		translateInfoList.forEach(propertyTranslateInfo -> {
			Field sourceField = propertyTranslateInfo.getSourceField();
			Field targetField = propertyTranslateInfo.getTargetField();
			if (null != sourceField && null != targetField) {
				String propertyCode = propertyTranslateInfo.getPropertyCode();
				try {
					String s = null;
//					StopWatch stop = new StopWatch();
//					stop.start();
					if (obj != null && sourceField.get(obj) != null) {
						s = sourceField.get(obj).toString();
						String propValueName = null;
//						String propCacheKey = getPropCacheKey(propertyCode, s);
//						if (propCache.containsKey(propCacheKey)) {
//							propValueName = propCache.get(propCacheKey);
//						} else {
						propValueName = propertyCache.get(propertyCode, s);
//							propCache.put(propCacheKey, propValueName);
//						}
						targetField.set(obj, propValueName);
					}
//					stop.stop();
//					System.out.println("设值耗费时间：" + stop.getTotalTimeNanos() + "ns 约" + stop.getTotalTimeMillis() + "ms");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	private String getPropCacheKey(String propCode, String codeId) {
		return propCode + StrUtil.C_UNDERLINE + codeId;
	}

	public static void main(String[] args) {
		long l = System.currentTimeMillis();
		try {
			Thread.sleep(10 * 1000);
			long l2 = System.currentTimeMillis();
			System.out.println(l2 - l);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

	/*
	@Override public void run(ApplicationArguments args) throws Exception {
		cacheLastRefreshTime = propertyCache.getRefreshTime();
	}
	 */
}