package cc.smtweb.framework.core.cache;

import cc.smtweb.framework.core.annotation.SwCache;
import cc.smtweb.framework.core.cache.redis.RedisBroadcastEvent;
import cc.smtweb.framework.core.cache.redis.RedisManager;
import cc.smtweb.framework.core.common.SwConsts;
import cc.smtweb.framework.core.util.CommUtil;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Scheduler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 封装caffeine的抽象Cache类，实现内存缓存，并支持用redis广播进行数据缓存同步
 *
 * @param <> 缓存的值类
 * @author zhenggm
 */
@Slf4j
public abstract class AbstractCache<T extends Serializable> implements ISwCache<String, T> {
    protected final static int LS_NONE = 0;
    protected final static int LS_LOADING = 1;
    protected final static int LS_LOADED = 2;

    //唯一标识
    protected String ident;
    //展示名称
    protected String title;
    //是否懒加载，懒加载时，将不从库loadall
    protected boolean lazy;
    //依赖
    protected String[] depends;

    //数据加载状态
    private short loadStatu = LS_NONE;
    protected Class<T> pTypeClass = null;


    private LoadingCache<String, T> cache;
    private Map<String, T> cacheOrg = new ConcurrentHashMap<>();
    //本地缓存对象，按List缓存，减轻redis压力
    protected Map<String, Set<T>> mapListLocal = new ConcurrentHashMap<>();
    //本地按其他键值缓存的对象
    protected Map<String, Map<String, T>> mapMapLocal = new ConcurrentHashMap<>();

    /*注册要按list缓存的信息，key=list的类别，按什么字段来缓存。IGetBeanKey为根据bean获取此bean的key
    如资金科目明细项，需按资金科目缓存List。key=sc,value=class_id
     */
    protected Map<String, IGetBeanKey<T>> mapListReg = new HashMap<>();
    protected Map<String, IGetBeanKeys<T>> mapListRegEx = new HashMap<>();
    protected Map<String, IGetBeanKey<T>> mapMapReg = new HashMap<>();

    public AbstractCache() {
        pTypeClass = CommUtil.getParameterizedType(getClass());
    }

    //注册
    protected void install(ScheduledExecutorService executorService) {
        SwCache swCache = this.getClass().getAnnotation(SwCache.class);
        if (swCache != null) {
            ident = swCache.ident();
            title = swCache.title();
            lazy = swCache.lazy();
            depends = StringUtils.split(swCache.depends(), ",");
        }
        final @NonNull Caffeine<Object, Object> kvCaffeine = Caffeine.newBuilder()
                .scheduler(Scheduler.forScheduledExecutorService(executorService));
        if (swCache != null && swCache.timeout() > 0) {
            kvCaffeine.expireAfterAccess(swCache.timeout(), TimeUnit.MINUTES);
        }
        this.cache = kvCaffeine.build(this::onLoad);
    }

    public boolean isNotInited() {
        return loadStatu == LS_NONE;
    }

    /**
     * 注册标识，用于同步数据事件通知
     */
    public String getIdent() {
        return ident;
    }

    public String getTitle() {
        return title;
    }

    public boolean isLazy() {
        return lazy;
    }

    public String[] getDepends() {
        return depends;
    }

    /**
     * 初始化
     * redis有，则从redis下载；
     * 否则，非lazy，从数据库加载，并更新redis
     */
    protected void init() {
        if (!SwConsts.SysParam.SYS_DEBUG && RedisManager.getInstance().exists(getIdent())) {
            log.info("从redis同步缓存！（" + getTitle() + "）");
            //从缓存服务器下载到本地
            syncCache();
        } else if (!lazy) {
            log.info("从数据库同步缓存！（" + getTitle() + "）");
            refresh();
        }
    }

    /**
     * 注册其他key的List缓存，如tree的children
     *
     * @param key
     * @param iGetBeanKey
     */
    protected void regList(String key, IGetBeanKey<T> iGetBeanKey) {
        mapListReg.put(key, iGetBeanKey);
    }

    protected void regListEx(String key, IGetBeanKeys<T> iGetBeanKey) {
        mapListRegEx.put(key, iGetBeanKey);
    }

    /**
     * 注册其他key的Map缓存，如按code缓存
     *
     * @param key
     * @param iGetBeanKey
     */

    protected void regMap(String key, IGetBeanKey<T> iGetBeanKey) {
        mapMapReg.put(key, iGetBeanKey);
    }

    //获取bean的id
    protected abstract String getId(T bean);

    /**
     * 从redis获取全部缓存
     * redis上只保留<id,Bean>，拉下来后，自行组织其他格式缓存
     */
    public void syncCache() {
        loadStatu = LS_LOADING;

        cache.invalidateAll();
        cacheOrg.clear();
        mapListLocal.clear();
        mapMapLocal.clear();

        List<byte[]> vals = RedisManager.getInstance().hVals(getIdent());
        for (byte[] v : vals) {
            T bean = CommUtil.readObject(v, pTypeClass);
            doUpdate(bean);
        }
        loadStatu = LS_LOADED;
    }

    /**
     * 监听redis同步事件
     *
     * @param event
     */
    public void syncCache(RedisBroadcastEvent event) {
        //加载中，忽略
        if (loadStatu == LS_LOADING) {
            log.info("加载中，忽略本次同步通知：" + event.toString());
            return;
        }
        switch (event.getAction()) {
            case RedisBroadcastEvent.CODE_REMOVE:
                doRemove(event.getKey());
                break;
            case RedisBroadcastEvent.CODE_CLEAR:
                doClear();
                break;
            case RedisBroadcastEvent.CODE_CACHE_UPDATE:
                T bean = RedisManager.getInstance().hGet(getIdent(), event.getKey(), pTypeClass);
                if (bean != null) {
                    doRemove(getId(bean));
                    doUpdate(bean);
                }
                break;
            case RedisBroadcastEvent.CODE_CACHE_REFRESH:
                syncCache();
                break;
            default:
                throw new IllegalStateException("Unexpected value: " + event.getAction());
        }
    }


    /**
     * 通过指定的key获取缓存对象值
     *
     * @param key 缓存key值
     * @return 缓存对象值
     */
    protected T load(String key) {
        return null;
    }

    /**
     * 从数据库加载全部,lazy模式可以不用实现
     */
    protected List<T> loadAll() {
        return null;
    }

    /**
     * @param key
     * @return
     */
    private T onLoad(String key) {
        if (lazy) {//非懒加载，不管
            T result = load(key);

            if (result != null) {
                this.put(result);
            }

            return result;
        }
        return null;
    }

    /**
     * 删除时，需要拿原来的对象；对象被取出后，可能被修改
     *
     * @param key
     * @return
     */
    public T getOrg(String key) {
        return cacheOrg.get(key);
    }

    /**
     * 本地删除缓存
     *
     * @param key
     */
    protected void doRemove(String key) {
        T oldbean = getOrg(key);
        T bean = get(key);
        if (bean != null) {
            for (Map.Entry<String, IGetBeanKey<T>> entry : mapListReg.entrySet()) {
                doRemoveList(entry.getKey(), getBeanKey(entry.getValue(), oldbean), bean);
            }

            for (Map.Entry<String, IGetBeanKeys<T>> entry : mapListRegEx.entrySet()) {
                String[] keys = entry.getValue().getKey(oldbean);
                for (String k : keys) {
                    doRemoveList(entry.getKey(), k, bean);
                }
            }

            for (Map.Entry<String, IGetBeanKey<T>> entry : mapMapReg.entrySet()) {
                doRemoveMap(entry.getKey(), getBeanKey(entry.getValue(), oldbean));
            }
        }
        cache.invalidate(key);
        cacheOrg.remove(key);
    }

    /**
     * 插入新对象
     */
    protected void doUpdate(T value) {
        final String key = getId(value);
        cache.put(key, value);
        cacheOrg.put(key, CommUtil.cloneObj(value, pTypeClass));

        for (Map.Entry<String, IGetBeanKey<T>> entry : mapListReg.entrySet()) {
            doUpdateList(entry.getKey(), getBeanKey(entry.getValue(), value), value);
        }

        for (Map.Entry<String, IGetBeanKeys<T>> entry : mapListRegEx.entrySet()) {
            String[] keys = entry.getValue().getKey(value);
            for (String k : keys) {
                doUpdateList(entry.getKey(), k, value);
            }
        }

        for (Map.Entry<String, IGetBeanKey<T>> entry : mapMapReg.entrySet()) {
            doUpdateMap(entry.getKey(), getBeanKey(entry.getValue(), value), value);
        }
    }

    //本地调用，删除一个对象时，更新列表缓存
    private void doRemoveList(String regionKey, String key, T value) {
        if (StringUtils.isEmpty(key)) return;
        Set<T> list = mapListLocal.get(regionKey + SwConsts.SPLIT_CHAR + key);
        if (list == null) {
            return;
        }
        list.remove(value);
    }

    //本地调用，删除对象时，更新map缓存
    private void doRemoveMap(String regionKey, String key) {
        if (StringUtils.isEmpty(key)) return;
        Map<String, T> map = mapMapLocal.get(regionKey);
        if (map != null) {
            map.remove(key);
        }
    }

    //本地调用，更新列表缓存
    private void doUpdateList(String regionKey, String key, T value) {
        if (StringUtils.isEmpty(key)) return;
        Set<T> list = mapListLocal.computeIfAbsent(regionKey + SwConsts.SPLIT_CHAR + key, k -> new LinkedHashSet<>());
        list.add(value);
    }

    //本地调用，更新对象时，更新map缓存
    private void doUpdateMap(String regionKey, String key, T value) {
        if (StringUtils.isEmpty(key)) return;
        Map<String, T> map = mapMapLocal.computeIfAbsent(regionKey, k -> new HashMap<>());
        map.put(key, value);
    }

    /**
     * 外部调用，更新缓存：先删除，再更新本地缓存，更新并通知redis
     *
     * @param bean
     */
    public final void put(T bean) {
        doRemove(getId(bean));
        doUpdate(bean);
        if (loadStatu != LS_LOADING) {
            RedisManager.getInstance().hSet(getIdent(), getId(bean), bean);
            //通知redis
            publishUpdate(getId(bean));
        }
    }

    public final void putList(Collection<T> list) {
        list.forEach(bean -> {
            put(bean);
        });
    }

    /**
     * 将对象重置为修改前的值，用于取出修改后，事务提交失败
     *
     * @param bean
     */
    public final void reset(T bean) {
        final String id = getId(bean);
        T b = getOrg(id);
        if (b != null) {
            cache.put(id, b);
        }
    }

    @Override
    public final T get(String key) {
        return cache.get(key);
    }

    public final T get(long key) {
        return cache.get(String.valueOf(key));
    }

    /**
     * 按其他key取bean
     *
     * @param rk  注册类型
     * @param key
     * @return
     */
    protected final T getByKey(String rk, String key) {
        Map<String, T> map = mapMapLocal.get(rk);
        if (map != null) {
            return map.get(key);
        }
        return null;
    }

    public final Set<T> getListByKey(String rk, String key) {
        return mapListLocal.get(rk + SwConsts.SPLIT_CHAR + key);
    }

    /**
     * 通知redis更新：更新单个bean
     *
     * @param key
     */
    protected final void publishUpdate(String key) {
        RedisBroadcastEvent message = new RedisBroadcastEvent();
        message.setIdent(getIdent());
        message.setKey(key);
        message.setAction(RedisBroadcastEvent.CODE_CACHE_UPDATE);
        RedisManager.getInstance().publish(message);
    }

    /**
     * 通知redis更新：删除单个bean
     *
     * @param key
     */
    @Override
    public final void publishRemove(String key) {
        RedisBroadcastEvent message = new RedisBroadcastEvent();
        message.setIdent(getIdent());
        message.setKey(key);
        message.setAction(RedisBroadcastEvent.CODE_REMOVE);
        RedisManager.getInstance().publish(message);
    }

    /**
     * 通知redis更新：清空缓存
     */
    @Override
    public final void publishClear() {
        RedisBroadcastEvent message = new RedisBroadcastEvent();
        message.setIdent(getIdent());
        message.setAction(RedisBroadcastEvent.CODE_CLEAR);
        RedisManager.getInstance().publish(message);
    }

    /**
     * 通知redis更新：刷新缓存     *
     */
    public final void publishRefresh() {
        RedisBroadcastEvent message = new RedisBroadcastEvent();
        message.setIdent(getIdent());
        message.setAction(RedisBroadcastEvent.CODE_CACHE_REFRESH);
        RedisManager.getInstance().publish(message);
    }

    /**
     * 外部调用，删除单个对象
     *
     * @param key
     */
    public void remove(String key) {
        this.doRemove(key);
        RedisManager.getInstance().hdel(getIdent(), key);
        publishRemove(key);
    }

    public void removeList(Collection<Long> key) {
        if (CommUtil.isEmpty(key)) return;
        key.forEach(k -> {
            remove(k);
        });
    }

    public void remove(long key) {
        remove(String.valueOf(key));
    }

    /**
     * 本地调用，清空缓存
     */
    protected void doClear() {
        cache.cleanUp();
        cacheOrg.clear();
        mapListLocal.clear();
        mapMapLocal.clear();
    }

    /**
     * 外部调用，清空缓存：清空本地缓存，并更新redis，通知其他服务
     */
    public void clear() {
        doClear();
        RedisManager.getInstance().del(getIdent());
        publishClear();
    }

    /**
     * 重载缓存：先清空，再重载
     */
    public void refresh() {
        loadStatu = LS_LOADING;
        doClear();
        List<T> list = loadAll();
        if (list != null) {
            for (T bean : list) {
                doUpdate(bean);
            }
        }
        loadStatu = LS_LOADED;
        RedisManager.getInstance().hmSet(getIdent(), cacheOrg);
        publishRefresh();
    }

    /**
     * 根据接口，得到bean的缓存key
     *
     * @param iGetBeanKey
     * @param bean
     * @return
     */
    protected String getBeanKey(IGetBeanKey<T> iGetBeanKey, T bean) {
        return iGetBeanKey.getKey(bean);
    }

    public Collection<T> getAll() {
        return cacheOrg.values();
    }

    /**
     * 获取bean缓存key的接口类，用于按非id的缓存，如code等
     *
     * @param <T>
     */
    public interface IGetBeanKey<T> {
        String getKey(T bean);
    }

    public interface IGetBeanKeys<T> {
        String[] getKey(T bean);
    }
}
