package me.qwq.doghouse.cache;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.conditions.Wrapper;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.annotation.CacheEvictTags;
import me.qwq.doghouse.annotation.CacheEvictTags.Tag;
import me.qwq.doghouse.entity.Post;
import me.qwq.doghouse.service.CommentService;
import me.qwq.doghouse.service.post.PostService;

/**
 * 
 * @author Doghole
 *
 */
@Aspect
@Component
@Slf4j
public class CacheEvictTagsAspect {

    @Autowired
    PostService postService;
    
    @Autowired
    CommentService commentService;
    
    @Autowired
    Reflections reflections;
    
    @Autowired
    CacheManager cacheManager;
    
    List<Field> fields = new ArrayList<>();
    
    Map<Tag, Set<String>> tagKeyCache = new HashMap<>();
    
    Map<CacheEvictTags, Map<Tag, Boolean>> containsTagCache = new HashMap<>();
    
    @PostConstruct
    void init() {
        // 使用 Reflections 查找所有添加了 CacheEvictTags 的 field
        Set<Field> fields = reflections.getFieldsAnnotatedWith(CacheEvictTags.class);
        this.fields = fields.stream()
                .filter(f -> f.getType().equals(String.class))
                .filter(f -> Modifier.isPublic(f.getModifiers()))
                .filter(f -> Modifier.isStatic(f.getModifiers())).toList();
    }
    
    @SuppressWarnings("unchecked")
    @Around("@annotation(annotation)")
    public Object around(ProceedingJoinPoint joinPoint, CacheEvictTags annotation) throws Throwable {
        Object[] args = joinPoint.getArgs();
        
        if (args.length != 1) {
            return joinPoint.proceed();
        }
        
        MethodSignature ms = (MethodSignature)joinPoint.getSignature();
        Method method = ms.getMethod();
        
        ResolvableType argType = ResolvableType.forMethodParameter(method, 0);
        Class<?> rawClass = argType.resolve();
        
        
        
        // 缓存 postName (若有)
        List<String> postNames = new ArrayList<>();
        List<Post> posts = new ArrayList<>();
        
        if (rawClass == Post.class) {
            // 单 Post，驱逐 PostId, PostName, Prev, Next
            postNames.add(((Post)args[0]).getPostPageName());
            posts.add((Post)args[0]);
        }
        else if (Collection.class.isAssignableFrom(rawClass)) {
            rawClass = argType.getGeneric(0).resolve();
            if (rawClass == Post.class) {
                // 批量
                posts = new ArrayList<>((Collection<Post>)args[0]);
                postNames.addAll(posts.stream().map(p -> p.getPostPageName()).toList());
            }
        }
        else if (Wrapper.class.isAssignableFrom(rawClass)) {
            rawClass = argType.getGeneric(0).resolve();
            if (rawClass == Post.class) {
                // 批量
                posts = postService.list((Wrapper<Post>)args[0]);
                postNames.addAll(posts.stream().map(p -> p.getPostPageName()).toList());
            }
        }
        
        Object result = joinPoint.proceed(args);
        
        if ((method.getReturnType() == Boolean.class || method.getReturnType() == boolean.class) && (Boolean)result) {
            // 清除批量类缓存
            batchEvictIfContainsTag(annotation, Tag.Comments);
            batchEvictIfContainsTag(annotation, Tag.Posts);
            batchEvictIfContainsTag(annotation, Tag.Metas);
            evictPosts(posts, annotation);
            evictPostNames(postNames, annotation);
        }
        
        
        return result;
    }
    
    /**
     * 清除 Post 有关缓存, 包括 ID, Prev 和 Next, 在更新成功后驱逐
     * @param posts
     */
    void evictPosts(Collection<Post> posts, CacheEvictTags anno) {
        
        if (posts == null || posts.isEmpty()) {
            return;
        }

        boolean containsPostId = containsTag(anno, Tag.PostId);
        boolean containsPrev = containsTag(anno, Tag.Prev);
        boolean containsNext = containsTag(anno, Tag.Next);
        
        Set<String> postIdKeys = containsPostId ? getKeysWithTag(Tag.PostId) : Set.of();
        Set<String> prevKeys = containsPrev ? getKeysWithTag(Tag.Prev) : Set.of();
        Set<String> nextKeys = containsNext ? getKeysWithTag(Tag.Next) : Set.of();
        
        
        
        for (Post post : posts) {
            for (String key : postIdKeys) {
                cacheManager.getCache(key).evictIfPresent(post.getPostId());
            }
            if (containsPrev) {
                for (Post prev : Arrays.asList(
                        postService.cacheableGetPreviousLogined(post.getPostId()),
                        postService.cacheableGetPreviousUnlogined(post.getPostId()))) {
                    if (prev != null) {
                        for (String key : prevKeys) {
                            cacheManager.getCache(key).evictIfPresent(prev.getPostId());
                        }
                    }
                }
            }
            if (containsNext) {
                for (Post next : Arrays.asList(
                        postService.cacheableGetNextLogined(post.getPostId()),
                        postService.cacheableGetNextUnlogined(post.getPostId()))) {
                    if (next != null) {
                        for (String key : nextKeys) {
                            cacheManager.getCache(key).evictIfPresent(next.getPostId());
                        }
                    }
                }
            }
        }
        
    }
    
    /**
     * 清除 PostName 有关缓存，在更新成功后驱逐，但其名称需要在更新前存储
     * @param postNames
     */
    void evictPostNames(Collection<String> postNames, CacheEvictTags anno) {
        
        if (postNames == null || postNames.isEmpty()) {
            return;
        }
        
        if (!containsTag(anno, Tag.PostName)) {
            return;
        }
        
        Set<String> postNameKeys = getKeysWithTag(Tag.PostName);
        
        for (String postName : postNames) {
            for (String key : postNameKeys) {
                cacheManager.getCache(key).evictIfPresent(postName);
            }
        }
    }
    
    /**
     * 批量清除指定 tag 的缓存
     * @param tag
     */
    void batchEvictIfContainsTag(CacheEvictTags anno, Tag tag) {
        if (!containsTag(anno, tag)) return;
        Set<String> keys = getKeysWithTag(tag);
        for (String key : keys) {
            cacheManager.getCache(key).clear();
        }
        
    }
    
    /**
     * 根据指定 tag 获取被其注解的缓存 key
     * @param tag
     * @return
     */
    Set<String> getKeysWithTag(Tag tag) {
        
        if (tagKeyCache.containsKey(tag)) {
            return tagKeyCache.get(tag);
        }
        
        Set<String> keys = new HashSet<>();
        
        for (Field field : fields) {
            CacheEvictTags anno = field.getAnnotation(CacheEvictTags.class);
            if (!containsTag(anno, tag)) continue;
            try {
                String key = (String)field.get(null);
                if (key == null) continue;
                keys.add(key);
            }
            catch (IllegalArgumentException | IllegalAccessException e) {
                log.warn("无法获取被 Tag: {} 注解的 field: {}", tag, field);
            }
        }
        tagKeyCache.put(tag, keys);
        return keys;
    }
    
    /**
     * 指定 CacheEvictTags 是否包含 Tag
     * @param anno
     * @param tag
     * @return
     */
    boolean containsTag(CacheEvictTags anno, Tag tag) {
        if (anno == null || tag == null) return false;
        return containsTagCache.computeIfAbsent(anno, 
            m -> new HashMap<Tag, Boolean>()
        ).computeIfAbsent(tag, b -> {
            Set<Tag> setA = EnumSet.copyOf(Arrays.asList(anno.value()));
            return !setA.isEmpty() && setA.contains(tag);
        });
        
    }

}
