package me.qwq.doghouse.service;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.Constant;
import com.github.yulichang.toolkit.LambdaUtils;
import com.github.yulichang.wrapper.MPJLambdaWrapper;

import me.qwq.doghouse.cache.CacheConstants;
import me.qwq.doghouse.dao.MetaMapper;
import me.qwq.doghouse.entity.Cate;
import me.qwq.doghouse.entity.ITreeView;
import me.qwq.doghouse.entity.Meta;
import me.qwq.doghouse.entity.Relationship;
import me.qwq.doghouse.entity.Tag;
import me.qwq.doghouse.entity.Post;
import me.qwq.doghouse.enums.MetaTypeEnum;
import me.qwq.doghouse.enums.PostStatusEnum;
import me.qwq.doghouse.enums.PostTypeEnum;
import me.qwq.doghouse.util.SpringContextHolder;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Meta 表服务实现类
 */
@Service
public class MetaService extends BaseServiceImpl<MetaMapper, Meta> {

    @Autowired
    MetaMapper metaMapper;
    
    @Autowired
    RelationshipService relationshipService;
    
    /**
     * 获取带计数的查询 Wrapper
     * <p>会自动判断是否登录，若需要缓存或使用的方法带缓存，请改用
     * {@link getCountDefaultWrapper(MetaTypeEnum metaTypeEnum, Boolean isLogin)}</p>
     * @param metaTypeEnum
     * @return
     */
    public MPJLambdaWrapper<?> getCountDefaultWrapper(MetaTypeEnum metaTypeEnum) {
        return getCountDefaultWrapper(metaTypeEnum, isLogin());
    }
    
    /**
     * 获取带计数的查询 Wrapper
     * @param metaTypeEnum
     * @param isLogin
     * @return
     */
    public MPJLambdaWrapper<?> getCountDefaultWrapper(MetaTypeEnum metaTypeEnum, Boolean isLogin) {
        return getCountDefaultWrapperWithPostTypes(metaTypeEnum, isLogin);
    }
    

    /**
     * 获取带计数的查询 Wrapper，并指定 PostType
     * @param metaTypeEnum
     * @param isLogin
     * @return
     */
    public MPJLambdaWrapper<?> getCountDefaultWrapperWithPostTypes(MetaTypeEnum metaTypeEnum, Boolean isLogin, PostTypeEnum...postTypes) {
        boolean isEmpty = ArrayUtils.isEmpty(postTypes);
        return new MPJLambdaWrapper<Meta>(Meta.class)
                .selectAll(Meta.class)
                .selectCount(Relationship::getPostId, LambdaUtils.getName(Meta::getPostCount))
                .join(!isLogin ? Constant.LEFT_JOIN : Constant.RIGHT_JOIN, Relationship.class, 
                        i -> i.eq(Relationship::getMid, Meta::getMid)).innerJoin(Post.class, i -> i
                .eq(Post::getPostId, Relationship::getPostId)
                .eq(Post::getIsDeleted, false)
                .in(!isEmpty, Post::getType, (Object[])postTypes)
                .nested(!isLogin, j -> j
                        .eq(Post::getPostStatus, PostStatusEnum.RELEASED)
                        .eq(Post::getSearchable, true))
                .ne(isLogin, Post::getPostStatus, PostStatusEnum.DRAFT))
                .eq(Meta::getType, metaTypeEnum)
                .groupBy(Meta::getMid);
    }
    
    public <T extends Meta & ITreeView<T>> MPJLambdaWrapper<T> getTreeViewMetaDefaultWrapper(Class<T> clazz) {
        /**
         * 这里虽然是 Cate 的查询
         * 但是如果 SelectAll 用 Cate.class
         * 由于 Cate 存在 Alias = "mc" 的缘故，查询条件将会变成 "mc.xxx"
         * 但 Count 时操作的是 Meta (SELECT COUNT(*) FROM meta) 不存在表别名 mc
         * 会导致查询错误，所以此处用 selectAll(Meta.class)
         * 其他对 Cate 或 Tag 的分页查询同理
         */
        
        MPJLambdaWrapper<T> wrapper = new MPJLambdaWrapper<T>()
                .selectAll(Meta.class)
                .leftJoin(Relationship.class, Relationship::getMid, Meta::getMid)
                .leftJoin(Post.class, Post::getPostId, Relationship::getPostId,
                        i-> i.eq(Post::getPostStatus, PostStatusEnum.RELEASED)
                        .eq(Post::getIsDeleted, false))
                .groupBy(Meta::getMid)
                .orderByDesc(Meta::getNo)
                .orderByAsc(Meta::getMid);
        
        if (Cate.class.equals(clazz)) {
            wrapper
            .selectFunc("COUNT(DISTINCT %s)", args -> args.accept(Post::getPostId), Cate::getPostCount)
            .selectFunc("COUNT(DISTINCT %s)", args -> args.accept(Cate::getMid), Cate::getChildrenCount)
            .eq(Meta::getType, MetaTypeEnum.CATEGORY)
            .leftJoin(Cate.class, Cate::getParentId, Meta::getMid);
        }
        else {
            if (clazz == null) {
                throw new NullPointerException("提供的类为 null");
            }
            throw new IllegalArgumentException(
                    String.format("暂未支持的类 %s", clazz.getName()));
        }
        
        return wrapper;
    }
    
    public MPJLambdaWrapper<Tag> getTagDefaultWrapper() {
        return new MPJLambdaWrapper<Tag>()
                .selectAll(Meta.class)
                .selectFunc("COUNT(DISTINCT %s)", args -> args.accept(Post::getPostId), LambdaUtils.getName(Tag::getPostCount))
                .leftJoin(Relationship.class, Relationship::getMid, Meta::getMid)
                .leftJoin(Post.class, Post::getPostId, Relationship::getPostId,
                        i-> i.eq(Post::getPostStatus, PostStatusEnum.RELEASED)
                        .eq(Post::getIsDeleted, false))
                .leftJoin(Tag.class, Tag::getParentId, Meta::getMid)
                .eq(Meta::getType, MetaTypeEnum.TAG)
                .groupBy(Meta::getMid)
                .orderByDesc(Meta::getNo)
                .orderByAsc(Meta::getMid);
    }
    
    /**
     * 查找指定 MetaTypeEnum 和其包含的文章(POST)的数量
     * @param metaTypeEnum
     * @param limit
     * @param isLogin
     * @return
     */
    public List<Meta> listMetaCount(MetaTypeEnum metaTypeEnum, Integer limit, PostTypeEnum...postTypeEnums) {
        MetaService metaService = SpringContextHolder.getBean("metaService");
        return metaService.listMetaCount(metaTypeEnum, limit, isLogin(), postTypeEnums);
    }
    
    /**
     * 用于缓存
     * @param metaTypeEnum
     * @param limit
     * @param isLogin
     * @return
     */
    @Cacheable(value=CacheConstants.Metas.LIST_META_COUNT)
    protected List<Meta> listMetaCount(MetaTypeEnum metaTypeEnum, Integer limit, Boolean isLogin, PostTypeEnum...postTypeEnums) {
        return listJoin(Meta.class, getCountDefaultWrapperWithPostTypes(metaTypeEnum, isLogin, postTypeEnums)
                    .orderByDesc(LambdaUtils.getName(Meta::getPostCount))
                    .orderByAsc(LambdaUtils.getName(Meta::getName))
                    .last(limit != null && limit > 0, " LIMIT " + limit)
                );
    }

    /**
     * 列出指定 parentId 下的分类
     * @param parentId 指定的 parentId
     * @param unfolded 包含需要展开的分类的 mid 的 List
     * @return
     */
    public <T extends Meta & ITreeView<T>> List<T> listTreeViewMetaWithChildren(Class<T> clazz, Long parentId, List<Long> unfolded) {
        return listTreeViewMetaWithChildren(clazz, getTreeViewMetaDefaultWrapper(clazz)
                .eq(Meta::getParentId, parentId), unfolded);
    }

    /**
     * 用指定 Wrapper 查询分类 list，并根据给定的 unfolded 决定是否递归
     * @param wrapper 查询 Wrapper&lt;Cate>
     * @param unfolded 包含需要展开的分类的 mid 的 List
     * @return
     */
    private <T extends Meta & ITreeView<T>> List<T> listTreeViewMetaWithChildren(Class<T> clazz, MPJLambdaWrapper<T> wrapper, List<Long> unfolded){
        List<T> list = listJoin(clazz, wrapper);
        for (T cate : list) {
            if (!CollectionUtils.isEmpty(unfolded) && unfolded.contains(cate.getMid())) {
                cate.setSpread(true);
                getTreeViewMetaChildren(cate, unfolded);
            }
        }
        return list;
    }
    
    
    /**
     * 递归获取从指定 MID 子级到父级的所有分类
     * 注意由于 Tag 和 Cate/WikiCate 是共用的，该方法也会作用于 Tag，所以这里默认了传入的 mid 对应的 Meta 类型不为 Tag
     * @param mid
     * @return
     */
    public List<Meta> listFromChildToParent(Long mid) {
        List<Meta> list = new ArrayList<>();
        Meta icate =  getOne(
                new MPJLambdaWrapper<Meta>(Meta.class).eq(Meta::getMid, mid));
        if (icate == null) {
            return list;
        }
        list.add(icate);
        Long parentId = icate.getParentId();
        while (!parentId.equals(0L)) {
            icate =  getOne(
                new MPJLambdaWrapper<Meta>(Meta.class).eq(Meta::getMid, icate.getParentId()));
            
            if (icate == null) {
                return list;
            }
            parentId = icate.getParentId();
            list.add(icate);
        }
        return list;
    }
    
    /**
     * 以指定分页器查询分类
     * @param pageQuery
     * @param unfolded 包含需要展开的分类的 mid 的 List
     * @return
     */
    public <T extends Meta & ITreeView<T>> IPage<T> pageTreeViewMetaWithChildren(Class<T> clazz, Page<T> pageQuery, List<Long> unfolded) {
        Page<T> page = joinPage(pageQuery, clazz,
                getTreeViewMetaDefaultWrapper(clazz)
                .eq(Meta::getParentId, 0));
        
        for(T icate : page.getRecords()) {
            if (unfolded != null && unfolded.contains(icate.getMid())) {
                icate.setSpread(true);
                getTreeViewMetaChildren(icate, unfolded);
            }
        }
        return page;
    }

    /**
     * 递归获取分类的子分类，并更新至传入的分类的 children 字段中
     * @param cate
     */
    @SuppressWarnings("unchecked")
    private <T extends Meta & ITreeView<T>> void getTreeViewMetaChildren(T cate, List<Long> unfolded) {
        if(cate == null) return;
        List<?> children = listJoin(cate.getClass(), getTreeViewMetaDefaultWrapper((Class<T>) cate.getClass())
                .eq(Meta::getParentId, cate.getMid())
                .groupBy(Meta::getMid)
                .orderByDesc(Meta::getNo));
        for (Object child : children) {
            if (unfolded.contains(((T)child).getMid())) {
                ((T)child).setSpread(true);
                getTreeViewMetaChildren((T)child, unfolded);
            }
        }
        cate.setChildren((List<T>)children);
    }
    
    /**
     * 根据 UpdateWrapper 条件，更新记录 需要设置sqlset，重写以用于缓存更新
     */
    @Override
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
                    CacheConstants.Posts.CONDITION_PAGE,
            }, allEntries=true)})
    public boolean update(Wrapper<Meta> updateWrapper) {
        return super.update(updateWrapper);
    }

    @Override
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
                    CacheConstants.Posts.CONDITION_PAGE,
            }, allEntries=true)})
    public boolean saveOrUpdate(Meta entity) {
        return super.saveOrUpdate(entity);
    }
    
    /**
     * 递归删除分类
     * @param cate
     */
    @SuppressWarnings("unchecked")
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
                    CacheConstants.Posts.CONDITION_PAGE,
            }, allEntries=true)})
    private <T extends Meta & ITreeView<T>> void removeTreeViewMeta(T cate) {
        if(cate == null) return;
        Class<T> clazz = (Class<T>) cate.getClass();
        List<T> children = listJoin(clazz, getTreeViewMetaDefaultWrapper(clazz)
                .eq(Meta::getParentId, cate.getMid())
                .groupBy(Meta::getMid)
                .orderByDesc(Meta::getNo));
        if (children != null) {
            for(T child : children) {
                removeTreeViewMeta(child);
            }
        }
        if (children == null || children.size() == 0) {
            relationshipService.remove(
                    new LambdaQueryWrapper<Relationship>()
                        .eq(Relationship::getMid, cate.getMid()));
            cate.deleteById();
        }
    }
    
    /**
     * 批量删除 Meta
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
                    CacheConstants.Posts.CONDITION_PAGE,
            }, allEntries=true)})
    public boolean removeByIds(Collection<?> mids) {
        for (Object mid : mids) {
            if (mid instanceof Serializable)
                removeById((Serializable)mid);
        }
        return true;
    }

    /**
     * 删除 Meta，同时更新 Relationship 表，同时更新缓存
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
            }, allEntries=true)})
    public boolean removeById(Serializable mid) {
        Meta meta = getOne(new MPJLambdaWrapper<Meta>(Meta.class).eq(Meta::getMid, mid));
        if (meta == null) {
            return true;
        }
        if (meta.getType() == MetaTypeEnum.CATEGORY) {
            // 递归删除
            removeTreeViewMeta(new Cate().setMid((Long)mid));
            return true;
        }
        return super.removeById(mid) 
            && relationshipService.remove(
                new LambdaQueryWrapper<Relationship>()
                    .eq(Relationship::getMid, mid));
    }

    /**
     * 删除所有未使用的标签
     */
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict={
            @CacheEvict(value= {
                    CacheConstants.Metas.LIST_META_COUNT,
                    CacheConstants.Posts.CONDITION_PAGE,
            }, allEntries=true)})
    public boolean removeUnusedTags() {
        List<Tag> unusedTags = 
            listJoin(Tag.class, getCountDefaultWrapper(MetaTypeEnum.TAG)
                    .having("COUNT(" + LambdaUtils.getName(Relationship::getPostId) + ") = 0")
            );
        
        return super.removeByIds(unusedTags.stream().map(i -> i.getMid()).collect(Collectors.toList()));
    }
    
    
    
}
