/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.common.utils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

import org.springblade.core.constant.BladeConstant;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.SpringUtil;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.github.benmanes.caffeine.cache.Caffeine;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 缓存工具类
 *
 * @author Chill
 */
public class CacheUtil {

	private static CacheManager cacheManager;

	private static final Boolean TENANT_MODE = Boolean.TRUE;

	/**
	 * 获取缓存工具
	 *
	 * @return CacheManager
	 */
	private static CacheManager getCacheManager() {
		if (cacheManager == null) {
			cacheManager = SpringUtil.getBean(CacheManager.class);
		}
		return cacheManager;
	}

	/**
	 * 获取缓存对象
	 *
	 * @param cacheName 缓存名
	 * @return Cache
	 */
	public static Cache getCache(String cacheName) {
		return getCache(cacheName, TENANT_MODE);
	}

	/**
	 * 获取缓存对象
	 *
	 * @param cacheName  缓存名
	 * @param tenantMode 租户模式
	 * @return Cache
	 */
	public static Cache getCache(String cacheName, Boolean tenantMode) {
		return getCacheManager().getCache(formatCacheName(cacheName, tenantMode));
	}

	/**
	 * 获取缓存对象
	 *
	 * @param cacheName 缓存名
	 * @param tenantId  租户ID
	 * @return Cache
	 */
	public static Cache getCache(String cacheName, String tenantId) {
		return getCacheManager().getCache(formatCacheName(cacheName, tenantId));
	}

	/**
	 * 根据租户信息格式化缓存名
	 *
	 * @param cacheName  缓存名
	 * @param tenantMode 租户模式
	 * @return String
	 */
	public static String formatCacheName(String cacheName, Boolean tenantMode) {
		if (!tenantMode) {
			return cacheName;
		}
		return formatCacheName(cacheName, AuthUtil.getTenantId());
	}

	/**
	 * 根据租户信息格式化缓存名
	 *
	 * @param cacheName 缓存名
	 * @param tenantId  租户ID
	 * @return String
	 */
	public static String formatCacheName(String cacheName, String tenantId) {
		return (StrUtil.isBlank(tenantId) ? cacheName : tenantId.concat(StringPool.COLON).concat(cacheName));
	}

	/**
	 * 获取缓存
	 *
	 * @param cacheName 缓存名
	 * @param keyPrefix 缓存键前缀
	 * @param key       缓存键值
	 * @return Object
	 */
	@Nullable
	public static Object get(String cacheName, String keyPrefix, Object key) {
		return get(cacheName, keyPrefix, key, TENANT_MODE);
	}

	/**
	 * 获取缓存
	 *
	 * @param cacheName  缓存名
	 * @param keyPrefix  缓存键前缀
	 * @param key        缓存键值
	 * @param tenantMode 租户模式
	 * @return Object
	 */
	@Nullable
	public static Object get(String cacheName, String keyPrefix, Object key, Boolean tenantMode) {
		if (Func.hasEmpty(cacheName, keyPrefix, key)) {
			return null;
		}
		Cache.ValueWrapper valueWrapper = getCache(cacheName, tenantMode).get(keyPrefix.concat(String.valueOf(key)));
		if (Func.isEmpty(valueWrapper)) {
			return null;
		}
		return valueWrapper.get();
	}

	/**
	 * 获取缓存
	 *
	 * @param cacheName 缓存名
	 * @param keyPrefix 缓存键前缀
	 * @param key       缓存键值
	 * @param type      类型
	 * @param <T>       泛型
	 * @return T
	 */
	@Nullable
	public static <T> T get(String cacheName, String keyPrefix, Object key, @Nullable Class<T> type) {
		return get(cacheName, keyPrefix, key, type, TENANT_MODE);
	}

	/**
	 * 获取缓存
	 *
	 * @param cacheName  缓存名
	 * @param keyPrefix  缓存键前缀
	 * @param key        缓存键值
	 * @param type       类型
	 * @param tenantMode 租户模式
	 * @param <T>        泛型
	 * @return T
	 */
	@SuppressWarnings("unchecked")
	@Nullable
	public static <T> T get(String cacheName, String keyPrefix, Object key, @Nullable Class<T> type, Boolean tenantMode) {
		if (Func.hasEmpty(cacheName, keyPrefix, key)) {
			return null;
		}
		return (T)getLevelOneCache(cacheName, tenantMode).get(keyPrefix.concat(String.valueOf(key)), k -> getCache(cacheName, tenantMode).get(k, type));
	}

	/**
	 * 获取缓存
	 *
	 * @param cacheName   缓存名
	 * @param keyPrefix   缓存键前缀
	 * @param key         缓存键值
	 * @param valueLoader 重载对象
	 * @param <T>         泛型
	 * @return T
	 */
	@Nullable
	public static <T> T get(String cacheName, String keyPrefix, Object key, Callable<T> valueLoader) {
		return get(cacheName, keyPrefix, key, valueLoader, TENANT_MODE);
	}

	//一级本地缓存
	final static Map<String, com.github.benmanes.caffeine.cache.Cache<String, Object>> cacheMap = new ConcurrentHashMap<>();
	
	/**
	 * 获取缓存
	 *
	 * @param cacheName   缓存名
	 * @param keyPrefix   缓存键前缀
	 * @param key         缓存键值
	 * @param valueLoader 重载对象
	 * @param tenantMode  租户模式
	 * @param <T>         泛型
	 * @return T
	 */
	@SuppressWarnings("unchecked")
	@Nullable
	public static <T> T get(String cacheName, String keyPrefix, Object key, Callable<T> valueLoader, Boolean tenantMode) {
		if (Func.hasEmpty(cacheName, keyPrefix, key)) {
			return null;
		}
		String _key = keyPrefix.concat(String.valueOf(key));
		T result = (T)getLevelOneCache(cacheName, tenantMode).getIfPresent(_key);
		if(result == null) {
			try {
				Cache.ValueWrapper valueWrapper = getCache(cacheName, tenantMode).get(_key);
				Object value = null;
				if (valueWrapper == null) {
					T call = valueLoader.call();
					if (ObjectUtil.isNotEmpty(call)) {
						Field field = ReflectUtil.getField(call.getClass(), BladeConstant.DB_PRIMARY_KEY);
						if (ObjectUtil.isNotEmpty(field) && ObjectUtil.isEmpty(ClassUtils.getMethod(call.getClass(), BladeConstant.DB_PRIMARY_KEY_METHOD).invoke(call))) {
							return null;
						}
						getCache(cacheName, tenantMode).put(_key, call);
						getLevelOneCache(cacheName, tenantMode).put(_key, call);
						value = call;
					}
				} else {
					value = valueWrapper.get();
					getLevelOneCache(cacheName, tenantMode).put(_key, value);
				}
				return (T) value;
			}  catch (Exception ex) {
				ex.printStackTrace();
				return null;
			}
		}
		return result;
	}

	/**
	 * 设置缓存
	 *
	 * @param cacheName 缓存名
	 * @param keyPrefix 缓存键前缀
	 * @param key       缓存键值
	 * @param value     缓存值
	 */
	public static void put(String cacheName, String keyPrefix, Object key, @Nullable Object value) {
		put(cacheName, keyPrefix, key, value, TENANT_MODE);
	}

	/**
	 * 设置缓存
	 *
	 * @param cacheName  缓存名
	 * @param keyPrefix  缓存键前缀
	 * @param key        缓存键值
	 * @param value      缓存值
	 * @param tenantMode 租户模式
	 */
	public static void put(String cacheName, String keyPrefix, Object key, @Nullable Object value, Boolean tenantMode) {
		//一级本地缓存
		getLevelOneCache(cacheName, tenantMode).put(keyPrefix.concat(String.valueOf(key)), value);
		//二级缓存
		getCache(cacheName, tenantMode).put(keyPrefix.concat(String.valueOf(key)), value);
	}
	
	/**
	 * 获取一级本地缓存
	 * @param cacheName
	 * @param tenantMode
	 * @return
	 */
	public static com.github.benmanes.caffeine.cache.Cache<String, Object> getLevelOneCache(String cacheName, Boolean tenantMode){
		String _cacheName = formatCacheName(cacheName, tenantMode);
		com.github.benmanes.caffeine.cache.Cache<String, Object> cache = cacheMap.get(_cacheName);
		if(cache == null) {
			cache = Caffeine.newBuilder().maximumSize(10_000).build();
			cacheMap.put(_cacheName, cache);
		}
		return cache;
	}

	/**
	 * 清除缓存
	 *
	 * @param cacheName 缓存名
	 * @param keyPrefix 缓存键前缀
	 * @param key       缓存键值
	 */
	public static void evict(String cacheName, String keyPrefix, Object key) {
		evict(cacheName, keyPrefix, key, TENANT_MODE);
	}

	/**
	 * 清除缓存
	 *
	 * @param cacheName  缓存名
	 * @param keyPrefix  缓存键前缀
	 * @param key        缓存键值
	 * @param tenantMode 租户模式
	 */
	public static void evict(String cacheName, String keyPrefix, Object key, Boolean tenantMode) {
		if (Func.hasEmpty(cacheName, keyPrefix, key)) {
			return;
		}
		cacheMap.getOrDefault(formatCacheName(cacheName, tenantMode), Caffeine.newBuilder().build()).invalidate(keyPrefix.concat(String.valueOf(key)));
		getCache(cacheName, tenantMode).evict(keyPrefix.concat(String.valueOf(key)));
	}

	/**
	 * 清空缓存
	 *
	 * @param cacheName 缓存名
	 */
	public static void clear(String cacheName) {
		cacheMap.remove(formatCacheName(cacheName, TENANT_MODE));
		clear(cacheName, TENANT_MODE);
	}

	/**
	 * 清空缓存
	 *
	 * @param cacheName  缓存名
	 * @param tenantMode 租户模式
	 */
	public static void clear(String cacheName, Boolean tenantMode) {
		if (Func.isEmpty(cacheName)) {
			return;
		}
		cacheMap.remove(formatCacheName(cacheName, tenantMode));
		getCache(cacheName, tenantMode).clear();
	}

	/**
	 * 清空缓存
	 *
	 * @param cacheName 缓存名
	 * @param tenantId  租户ID
	 */
	public static void clear(String cacheName, String tenantId) {
		if (Func.isEmpty(cacheName)) {
			return;
		}
		cacheMap.remove(formatCacheName(cacheName, tenantId));
		getCache(cacheName, tenantId).clear();
	}

	/**
	 * 清空缓存
	 *
	 * @param cacheName 缓存名
	 * @param tenantIds 租户ID集合
	 */
	public static void clear(String cacheName, List<String> tenantIds) {
		if (Func.isEmpty(cacheName)) {
			return;
		}
		tenantIds.forEach(tenantId -> {getCache(cacheName, tenantId).clear();cacheMap.remove(formatCacheName(cacheName, tenantId));});
	}

}
