package me.qwq.doghouse.service.post;

import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.annotation.RequireSession;
import me.qwq.doghouse.entity.config.WikiConfig;
import me.qwq.doghouse.entity.Post;
import me.qwq.doghouse.enums.PostStatusEnum;
import me.qwq.doghouse.enums.PostTypeEnum;
import me.qwq.doghouse.exception.PageNotFoundException;
import me.qwq.doghouse.exception.RException;
import me.qwq.doghouse.pojo.bo.PostPassJar;
import me.qwq.doghouse.pojo.dto.LayPageReq;
import me.qwq.doghouse.util.CookieUtils;
import me.qwq.doghouse.util.SessionUtils;

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.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.github.yulichang.wrapper.enums.DefaultFuncEnum;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * 文章服务
 *
 * @author Doghole
 */
@Service
@Slf4j
public class WikiService extends AbstractPostService<WikiService, WikiConfig> {

    static final String LIMIT_ = "LIMIT ";
    static final String LIMIT_1 = LIMIT_ + "1";
    
    public static final String KEY_ID = "#id";
    public static final String KEY_NAME = "#name";
    public static final String KEY_PREVIOUS_ID = "#previousId";
    public static final String KEY_NEXT_ID = "#nextId";
    public static final String UNFOLDED_WIKIS = "unfolded_wikis";
    
    
    /**
     * 获取通用查询 Post 的 Wrapper<br>默认排序
     * 注意该方法并未指定是否登录，如有需要，则由使用者指定
     * @param treatAsLogined 是否当做已登录
     * @return
     */
    public MPJLambdaWrapper<Post> getWikiDefaultWrapper(boolean treatAsLogined) {
        return getWikiDefaultWrapper(true, treatAsLogined);
    }
    
    /**
     * 获取通用查询 Post 的 Wrapper<br> 指定是否需要默认排序
     * @param ordered
     * @param isLogin
     * @return
     */
    public MPJLambdaWrapper<Post> getWikiDefaultWrapper(boolean ordered, boolean treatAsLogined) {
        return getPostCommonWrapper()
                .eq(Post::getType, PostTypeEnum.WIKI)
                .eq(Post::getIsDeleted, false)
                .eq(!treatAsLogined, Post::getPostStatus, PostStatusEnum.RELEASED)
                .selectFunc(DefaultFuncEnum.COUNT.getSql(), i -> i.accept(Post::getPostId), Post::getChildrenCount)
                .leftJoin(Post.class, "sub_wiki", i -> i
                        .eq(Post::getParentId, Post::getPostId)
                        .eq(Post::getIsDeleted, false)
                        .eq(!treatAsLogined, "sub_wiki", Post::getPostStatus, PostStatusEnum.RELEASED))
                .orderByAsc(ordered, Post::getNo, Post::getLevel, Post::getCreateTime);
    }
    
    /**
     * 递归用，Post 重写
     * @param id
     * @param isLogin
     * @return
     */
    @Override
    public Post getPrevious(Long id, Boolean isLogin) {
        Post curr = proxy().getById(id);
        Long parentId = curr.getParentId();
        Post prev = getOne(this.getWikiDefaultWrapper(false, isLogin)
                .eq(Post::getParentId, parentId)
                .lt(Post::getNo, curr.getNo())
                .orderByDesc(Post::getNo, Post::getLevel, Post::getCreateTime)
                .last(LIMIT_1));
        // 有前一篇且有子级应该直接取最子级
        if (prev != null) {
            Post leastChild = getLeastChild(prev);
            if (leastChild != null) {
                return leastChild;
            }
            // 如果没有子级，取本级
            return prev;
        }
        if (prev == null && parentId != -1L) {
            // 同级前一篇没了，但是存在父级，就去找父级
            Post parent = getOne(new LambdaQueryWrapper<Post>().eq(Post::getPostId, parentId));
            if (StringUtils.isNotEmpty(parent.getPostContent())) {
                // 父级有内容，直接返回父级
                return parent;
            }
            // 父级没内容，父级的前一篇呢
            return getPrevious(parent.getPostId(), isLogin);
        }
        return prev;
    }
    
    /**
     * 递归用，Post 重写
     * @param id
     * @param getFirstChild
     * @param isLogin
     * @return
     */
    @Override
    public Post getNext(Long id, Boolean getFirstChild, Boolean isLogin) {
        Post curr = proxy().getById(id);
        if (getFirstChild && PostPassJar.validFromSession(session, curr)) {
            this.getWikiChildren(curr, null);
            if (curr.getHasChildren()) {
                for (Post child : curr.getChildren()) {
                    if (StringUtils.isEmpty(child.getPassword())) {
                        return child;
                    }
                }
            }
        }
        Long parentId = curr.getParentId();
        Post next = getOne(getWikiDefaultWrapper(isLogin)
                .eq(Post::getParentId, parentId)
                .gt(Post::getNo, curr.getNo())
                .last(LIMIT_1));
        if (next != null) {
            return next;
        }
        else {
            if (parentId == -1L) {
                // 已经没父级了，而本级下一篇为空，说明真没了
                return next;
            }
            return getNext(parentId, false, isLogin);
        }
        // 同级下一篇为空，则找父级下一篇
        // 否则找父级下一篇，但是不找父级的子级了，否则循环了
    }

    public List<Post> listFrontWikis(Long parentId, List<Long> unfolded) {
        
        if (parentId == null) {
            parentId = -1L;
        }
        MPJLambdaWrapper<Post> ew = getWikiDefaultWrapper(true)
                .eq(Post::getParentId, parentId)
                .eq(Post::getIsDeleted, false);
        return listWikiWithChildren(ew, unfolded);
    }
    
    /**
     * 获取指定 parent 下能给前台呈现的子级
     * @param parent
     * @return
     */
    protected Post getLeastChild(Post parent) {
        if (parent == null || !PostPassJar.validFromSession(session, parent)) {
            return null;
        }
        Post linealLeastChild = getOne(
                getWikiDefaultWrapper(false)
                .eq(Post::getParentId, parent.getPostId())
                .orderByDesc(Post::getNo, Post::getLevel, Post::getCreateTime)
                .last(LIMIT_1));
        if (linealLeastChild == null) return null;
        if (!PostPassJar.validFromSession(session, linealLeastChild)) return null;
        if (!linealLeastChild.getHasChildren()) return linealLeastChild;
        return getLeastChild(linealLeastChild);
    }
    
    /**
     * (不校验登录和密码)获取层级分页 for <code><b>PostTypeEnum.<i>WIKI</i></b></code>
     * @param pageQuery 分页器
     * @param wrapper 查询条件
     * @param unfolded 需要展开的 id 列表
     * @return 分页模型
     */
    public IPage<Post> pageWikiWithChildren(LayPageReq<Post> pageQuery, MPJLambdaWrapper<Post> wrapper, List<Long> unfolded) {
        Page<Post> page = page(
                new Page<>(pageQuery.getPage(), pageQuery.getLimit())
                , wrapper);
        
        for (Post wiki : page.getRecords()) {
            if (unfolded != null && unfolded.contains(wiki.getPostId())) {
                wiki.setSpread(true);
                unfolded.remove((Object)wiki.getPostId());
                getWikiChildren(wiki, unfolded);
            }
        }
        return page;
    }

    /**
     * 带条件的列出子级 Post for <code><b>PostTypeEnum.<i>WIKI</i></b></code>
     * @param wrapper 查询条件
     * @param unfolded 需要展开的 id 列表
     * @return 子级列表
     */
    public List<Post> listWikiWithChildren(MPJLambdaWrapper<Post> wrapper,
            List<Long> unfolded) {
        Boolean isLogin = isLogin();
        List<Post> list = list(wrapper
                .eq(!isLogin, Post::getPostStatus, PostStatusEnum.RELEASED));
        for (Post wiki : list) {
            if (!PostPassJar.validFromSession(session, wiki)) {
                continue;
            }
            if (!CollectionUtils.isEmpty(unfolded) && unfolded.contains(wiki.getPostId())) {
                wiki.setSpread(true);
                unfolded.remove((Object)wiki.getPostId());
                getWikiChildren(wiki, unfolded);
            }
        }
        return list;
    }
    /**
     * 递归获取子级
     * @param wiki 父级
     * @param unfolded 需要展开的 id 列表
     */
    public void getWikiChildren(Post wiki, List<Long> unfolded) {
        if (wiki == null) return;
        Boolean isLogin = isLogin();
        List<Post> children = list(
            getWikiDefaultWrapper(true)
                .eq(Post::getIsDeleted, false)
                .eq(Post::getParentId, wiki.getPostId())
                .and(w -> w
                    .eq(Post::getPostStatus, PostStatusEnum.RELEASED)
                    .or(isLogin != null && isLogin)
                    .in(isLogin != null && isLogin, Post::getPostStatus, 
                        PostStatusEnum.PRIVATE, PostStatusEnum.HIDDEN, PostStatusEnum.DRAFT)));
        for (Post subWiki : children) {
            if (!PostPassJar.validFromSession(session, subWiki)) {
                continue;
            }
            if (unfolded != null && unfolded.contains(subWiki.getPostId())) {
                subWiki.setSpread(true);
                unfolded.remove((Object)wiki.getPostId());
                getWikiChildren(subWiki, unfolded);
            }
        }
        wiki.setChildren(children);
    }
    
    /**
     * 按照指定 parent 的层级，设置其下所有子级的 level，并返回所有子级的列表
     * @param parent
     * @return
     */
    public List<Post> setChildrenLevel(Post parent) {
        List<Post> result = new ArrayList<>();
        if (parent == null) {
            return result;
        }
        List<Post> children = list(
                getWikiDefaultWrapper(isLogin())
                .eq(Post::getIsDeleted, false)
                .eq(Post::getParentId, parent.getPostId()));
        for (Post child : children) {
            child.setLevel(parent.getLevel() + 1);
            result.addAll(setChildrenLevel(child));
        }
        result.addAll(children);
        return result;
    }
    
    /**
     * 按照从子到父的顺序，递归获取从指定 postId 到父级的所有 Post<br>
     * 由于目前，层级只在 <code><b>PostTypeEnum.<i>WIKI</i></b></code> 上实现，
     * 该方法也仅用于该类 Post
     * @param postId
     * @param containsSelf 返回列表是否包含本级
     * @param treatAsLogined 是否当做已登录
     * @return
     */
    public List<Post> listFromChildToParent(Long postId, Boolean containsSelf, Boolean treatAsLogined) {
        List<Post> list = new ArrayList<>();
        Post post = getOne(
                getWikiDefaultWrapper(treatAsLogined).eq(Post::getPostId, postId));
        if (post == null) {
            return list;
        }
        if (containsSelf) {
            list.add(post);
        }
        Long parentId = post.getParentId();
        while (!parentId.equals(-1L)) {
            post = getOne(
                    getWikiDefaultWrapper(treatAsLogined).eq(Post::getPostId, parentId));
            
            if (post == null) {
                return list;
            }
            parentId = post.getParentId();
            list.add(post);
        }
        return list;
    }
    
    /**
     * 用于移动及移动后的批量更改排序和层级，需提前检查父子从属关系
     * @param postIds 被移动的 postId 集合
     * @param to 移动至的 postId
     * @return 操作成功
     * @throws RException
     */
    public Boolean batchMoveTo(Collection<Long> postIds, Long to) {
        
        Boolean isLogin = isLogin();
        
        List<Post> batches = list(getWikiDefaultWrapper(isLogin)
                .in(Post::getPostId, postIds));
        
        Post currMaxNoPost = getOne(getWikiDefaultWrapper(false, isLogin)
                .eq(Post::getParentId, to)
                .notIn(Post::getPostId, postIds)
                .eq(Post::getIsDeleted, false)
                .orderByDesc(Post::getNo).last("LIMIT 1"));
        
        int start = 0;
        if (currMaxNoPost != null) {
            start = currMaxNoPost.getNo() + 1;
        }
        
        Post parent = getOne(getWikiDefaultWrapper(isLogin)
                .eq(Post::getPostId, to));
        int level = 0;
        if (parent != null) {
            level = parent.getLevel() + 1;
        }
        for (Post target : batches) {
            target.setLevel(level);
            if (target.getParentId().equals(to)) {
                continue;
            }
            target.setNo(start++);
            target.setParentId(to);
        }

        if (!proxy().saveOrUpdateBatch(batches)) {
            throw RException.badRequest("批量操作失败");
        }
        
        // 重新获取一遍移动后的结果，并更新子级的 level
        batches = list(getWikiDefaultWrapper(isLogin)
                .in(Post::getPostId, postIds));
        List<Post> allChildrenFlat = new ArrayList<>();
        
        for (Post post : batches) {
            allChildrenFlat.addAll(setChildrenLevel(post));
        }
        
        if (allChildrenFlat.isEmpty()) {
            return true;
        }
        
        if (!saveOrUpdateBatch(allChildrenFlat)) {
            log.error("重设子层级的 level 失败");
            throw RException.badRequest("批量操作失败");
        }
        
        return true;
    }
    
    /**
     * 重排指定父 ID 下的所有子 ID
     * @param parentId
     * @return
     */
    public Boolean reorderChildren(Long parentId) {

        Boolean isLogin = isLogin();
        
        List<Post> children = 
                list(getWikiDefaultWrapper(isLogin).eq(Post::getParentId, parentId));
        
        if (children.isEmpty()) return true;
        
        for (int i = 0; i < children.size(); i++) {
            children.get(i).setNo(i);
        }
        
        return proxy().saveOrUpdateBatch(children);
    }

    /**
     * 删除指定父级文章下的子级（不包含父级），当前仅对 WIKI 生效
     * @param parent 指定父级
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteChildren(Post parent) {
        if (parent == null || parent.getType() != PostTypeEnum.WIKI) {
            return true;
        }
        getWikiChildren(parent, null);
        if (!parent.getHasChildren()) return true;
        for (Post child : parent.getChildren()) {
            child.setIsDeleted(true);
            Boolean childUpdated = deleteChildren(child);
            if (!childUpdated) {
                log.warn("重设 postId={}, title={} 子项删除状态失败", child.getPostId(), child.getTitle());
                return false;
            }
        }
        return proxy().saveOrUpdateBatch(parent.getChildren());
    }

    /**
     * 重设指定父级文章下的子级（不包含父级）的可搜索状态，当前仅对 WIKI 生效
     * @param parent 指定父级
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean resetSearchableStatus(Post parent) {
        if (parent == null || parent.getType() != PostTypeEnum.WIKI) {
            return true;
        }
        getWikiChildren(parent, null);
        if (!parent.getHasChildren()) return true;
        for (Post child : parent.getChildren()) {
            if (child.getSearchable() && 
                (parent.getPostStatus() != PostStatusEnum.RELEASED ||
                 !"".equals(parent.getPassword()))) {
                // 子项公开，但父项加密或非公开，则不应被搜索到
                child.setSearchable(false);
            }
            else if(!child.getSearchable()
                    && (parent.getPostStatus() == PostStatusEnum.RELEASED &&
                            StringUtils.isEmpty(parent.getPassword()))
                    && child.getPostStatus() == PostStatusEnum.RELEASED) {
                // 子项不公开，但父项为非加密公开项，且子项也是公开项，则可被搜索到。
                // 此时子项若有加密，则在前端的显示应该为加密。对应的加密文章显示逻辑
                // 样式由前端控制。
                child.setSearchable(true);
            }
            Boolean childUpdated = resetSearchableStatus(child);
            if (!childUpdated) {
                log.warn("重设 postId={}, title={} 子项可搜索状态失败", child.getPostId(), child.getTitle());
                return false;
            }
        }
        return proxy().saveOrUpdateBatch(parent.getChildren());
    }

    /**
     * 指定 post id 的上移/下移
     * @param postId 指定的 postId
     * @param up 是否上移
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Boolean reorder(Long postId, boolean up) {
        Post targetPost = getOne(
                getWikiDefaultWrapper(true)
                .eq(Post::getPostId, postId));
        if (targetPost == null) {
            throw RException.badRequest("欲操作的 Post 不存在，请检查");
        }
        
        List<Post> sameLevels = list(
                getWikiDefaultWrapper(true)
                .eq(Post::getIsDeleted, false)
                .eq(Post::getParentId, targetPost.getParentId()));

        int index = sameLevels.indexOf(targetPost);
        
        if (up) {
            if (index == 0) {
                throw RException.badRequest("已是当前首位");
            }
        }
        else {
            if (index == sameLevels.size() - 1) {
                throw RException.badRequest("已是当前末位");
            }
        }
        
        for (int i = 0; i < sameLevels.size(); i++) {
            if (up) {
                if (i == index - 1) {
                    sameLevels.get(i).setNo(i + 1);
                    continue;
                }
                if (i == index) {
                    sameLevels.get(i).setNo(i - 1);
                    continue;
                }
            }
            else {
                if (i == index + 1) {
                    sameLevels.get(i).setNo(i - 1);
                    continue;
                }
                if (i == index) {
                    sameLevels.get(i).setNo(i + 1);
                    continue;
                }
            }
            sameLevels.get(i).setNo(i);
        }
        return proxy().saveOrUpdateBatch(sameLevels);
    }
    
    /**
     * 获取给定子项集合的对应父项的 postId 集合（已去重）
     * @param childrenIds 给定子项集合
     * @return 对应父项的 postId 集合（已去重）
     */
    public List<Long> getParentIds(Collection<Long> childrenIds) {
        return baseMapper.getParentIds(childrenIds);
    }
    
    @Override
    @RequireSession
    public void beforeSaving(
            Post post,
            Long[] assets,
            String[] tagsName,
            Long[] categoriesId,
            Boolean isUpdate) {
        super.beforeSaving(post, assets, tagsName, categoriesId, isUpdate);
        if (!isUpdate) {
            if (post.getParentId() == null) {
                post.setParentId(-1L);
            }
            // 自增 order no
            Post currMaxNoPost = getOne(
                    getWikiDefaultWrapper(false, true)
                    .eq(Post::getParentId, post.getParentId())
                    .orderByDesc(Post::getNo, Post::getLevel, Post::getCreateTime)
                    .last("LIMIT 1"));
            if (currMaxNoPost == null) {
                post.setNo(0);
            }
            else {
                post.setNo(currMaxNoPost.getNo() + 1);
            }
            
            // level
            if (post.getParentId() == -1L) {
                post.setLevel(0);
            }
            else {
                Post parent = getOne(
                        getWikiDefaultWrapper(true)
                        .eq(Post::getPostId, post.getParentId()).last("LIMIT 1"));
                if (parent == null) {
                    throw new RuntimeException("父 Post 不存在");
                }
                post.setLevel(parent.getLevel() + 1);
            }
        }
        // 覆盖 searchable 状态
        List<Post> toParent = listFromChildToParent(
                post.getParentId(), true, true);
        for (Post parent : toParent) {
            if (parent.getPostStatus() != PostStatusEnum.RELEASED ||
                    !"".equals(parent.getPassword())) {
                post.setSearchable(false);
                break;
            }
        }
    }
    
    @Override
    public void afterSaving(
            Post post,
            Long[] assets,
            String[] tagsName,
            Long[] categoriesId,
            Boolean isUpdate) {
        if (isUpdate && !resetSearchableStatus(post)) {
            throw new RuntimeException("重设子项搜索状态失败，请查看输出日志");
        }
        super.afterSaving(post, assets, tagsName, categoriesId, isUpdate);
    }
    
    @Override
    @RequireSession
    public Optional<String> detailCheckAndJump(Post post) {
        
        // 递归获取子级到顶级，并验证父级是否有密码或者私密，如果有则 404，因为这属于直接撞 postId
        List<Post> parents = listFromChildToParent(post.getPostId(), false, true);
        List<Long> unfolded = getUnfoldedWikis(request);
        
        if (unfolded == null) unfolded = new ArrayList<>();
        
        for (Post parent : parents) {
            if (!PostPassJar.validFromSession(session, parent) || (!SessionUtils.isLogin(session) && (
                    parent.getPostStatus() == PostStatusEnum.PRIVATE ||
                    parent.getPostStatus() == PostStatusEnum.DRAFT))) {
                throw new PageNotFoundException();
            }
            // unfolded_wikis 依次添加父级
            if (!unfolded.contains(parent.getPostId())) {
                unfolded.add(parent.getPostId());
            }
        }
        // 加入 Session
        session.setAttribute(UNFOLDED_WIKIS, unfolded);
        // 获取子级
        getWikiChildren(post, null);
        
        // 文章加密状态密码校验、清除
        boolean unlocked = PostPassJar.validFromSession(session, post);
        if (unlocked) {
            Boolean containsPostId = SessionUtils.containsPostId(session, post.getPostId());
            if (!containsPostId)
                proxy().updateViews(post);
            SessionUtils.addPostId(session, post.getPostId());
            if (StringUtils.isEmpty(post.getPostContent())
                    && post.getHasChildren()) {
                return Optional.of("redirect:" + post.getChildren().get(0).getLink());
            }
        }
        
        return Optional.empty();
        
    }
    

    /**
     * 从请求中获取 UNFOLDED_WIKIS 编号列表<br/>
     * 列表有两个来源：
     * <ol>
     * <li>Session，</li>
     * <li>Cookie</li>
     * </ol>
     * @param request
     * @return
     */
    public static List<Long> getUnfoldedWikis(HttpServletRequest request) {
        // Session 优先级大于 Cookie
        HttpSession session = request.getSession(false);
        if (session != null) {
            try {
                @SuppressWarnings("unchecked")
                List<Long> result = (List<Long>)session.getAttribute(UNFOLDED_WIKIS);
                // 用完要删
                session.removeAttribute(UNFOLDED_WIKIS);
                if (result != null)
                    return result;
            }
            catch (Exception e) {}
        }
        Cookie cookie = CookieUtils.getCookie(request, UNFOLDED_WIKIS);
        if (cookie == null) new ArrayList<>();
        try {
            return new ObjectMapper().readValue(
                    cookie.getValue(),
                    new TypeReference<List<Long>>() {}
            );
        }
        catch (Exception e) {
            return null;
        }
    }
}
