package me.qwq.doghouse.controller.blog;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import me.qwq.doghouse.component.PostTypeComponent;
import me.qwq.doghouse.controller.common.BaseController;
import me.qwq.doghouse.entity.*;
import me.qwq.doghouse.entity.config.SiteConfig;
import me.qwq.doghouse.entity.Post;
import me.qwq.doghouse.enums.CommentStatusEnum;
import me.qwq.doghouse.enums.MetaTypeEnum;
import me.qwq.doghouse.enums.PostStatusEnum;
import me.qwq.doghouse.enums.PostTypeEnum;
import me.qwq.doghouse.interfaces.IPostTypeConfig;
import me.qwq.doghouse.pojo.dto.PageQuery;
import me.qwq.doghouse.service.*;
import me.qwq.doghouse.service.post.PostService;
import me.qwq.doghouse.util.DateUtils;

import org.apache.commons.lang3.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.Text;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.UriUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;



/**
 * Sitemap Controller
 *
 * @author Doghole
 */
@Controller
public class SitemapController extends BaseController {

    public final static String CATEGORY_PATH = "/category/";
    public final static String TAG_PATH = "/tag/";
    public final static String SITEMAP_PATH = "/sitemap.xml";
    public final static String URL = "url";
    public final static String LOC = "loc";
    public final static String LASTMOD = "lastmod";
    public final static String UTF8 = "UTF-8";
    public final static String DATE_FORMAT = "yyyy-MM-dd";
    public final static Format FORMAT = Format.getCompactFormat().setEncoding(UTF8);
    public final static Namespace NAMESPACE = Namespace.getNamespace("http://www.sitemaps.org/schemas/sitemap/0.9"); 

    @Autowired
    PostService postService;

    @Autowired
    MetaService metaService;
    
    @Autowired
    CommentService commentService;
    
    @Autowired
    ConfigService configService;
    
    @Autowired
    SiteConfig siteConfig;
    
    @Autowired
    PostTypeComponent postTypeComponent;
    
    @GetMapping(value=SITEMAP_PATH, produces = "application/xml")
    @ResponseBody
    public String feed() {
        
        String siteAddress = siteConfig.getAddress();
        Document doc = new Document();

        Element urlset = new Element("urlset", NAMESPACE);
        LambdaQueryWrapper<Post> ew = new LambdaQueryWrapper<Post>()
                .eq(Post::getPostStatus, PostStatusEnum.RELEASED)
                .eq(Post::getIsDeleted, false)
                .eq(Post::getSearchable, true)
                .orderByDesc(Post::getType, Post::getCreateTime);
        
        for (PostTypeEnum e : PostTypeEnum.values()) {
            IPostTypeConfig<?> postTypeConfig = postTypeComponent.getPostTypeConfig(e);
            if (!postTypeConfig.getEnabled() || postTypeConfig.getAddToSitemap()) {
                ew.ne(Post::getType, e);
            }
        }
        
        List<Post> releasePosts = postService.list(ew);
        
        List<Element> urls = new ArrayList<>();
        
        for (Post post : releasePosts) {
            if (post.getType() == PostTypeEnum.WIKI && StringUtils.isEmpty(post.getPostContent())) {
                // 空 WIKI 文章不加入 Sitemap
                continue;
            }
            urls.add(getPostUrlElement(post, siteAddress));
        }
        
        List<Cate> releaseCates = metaService.listJoin(Cate.class, 
                metaService.getCountDefaultWrapper(MetaTypeEnum.CATEGORY)
                .selectMax(Post::getUpdateTime, Cate::getUpdateTime)
                //.selectFuncs(Post::getUpdateTime, Cate::getUpdateTime, FunctionPool.MAX)
                );
        List<Tag> releaseTags = metaService.listJoin(Tag.class,
                metaService.getCountDefaultWrapper(MetaTypeEnum.TAG)
                .selectMax(Post::getUpdateTime, Tag::getUpdateTime)
                //.selectFuncs(Post::getUpdateTime, Cate::getUpdateTime, FunctionPool.MAX)
                );
        for (Cate cate : releaseCates) {
            if (cate.getPostCount() == 0) continue;
            urls.add(getMetaUrlElement(cate, siteAddress));
        }
        for (Tag tag : releaseTags) {
            if (tag.getPostCount() == 0) continue;
            urls.add(getMetaUrlElement(tag, siteAddress));
        }
        
        // 首页的 lastmod 一般应该由以下几个部分组成：
        // 1. 首页文章的 lastmod
        // 2. 最新评论的 lastmod
        LocalDateTime date = LocalDateTime.of(1991, 8, 6, 0, 0);
        PageQuery<?> indexPage = postService.conditionPage(new PageQuery<>(PostTypeEnum.POST));
        for (Post post : indexPage.getRecords()) {
            if (date.isBefore(post.getUpdateTime())) {
                date = post.getUpdateTime();
            }
        }
        List<Comment> latestComments = commentService.getLatestComments(1L);
        if (!latestComments.isEmpty()) {
            if (date.isBefore(latestComments.get(0).getCreateTime())) {
                date = latestComments.get(0).getCreateTime();
            }
        }

        Element url = new Element(URL, NAMESPACE);
        Element loc = new Element(LOC, NAMESPACE);
        Element lastmod = new Element(LASTMOD, NAMESPACE);
        Text locNode = new Text(
                String.format("%s", siteAddress));
        loc.addContent(locNode);
        Text lastmodNode = new Text(
                DateUtils.format(date, DATE_FORMAT));
        lastmod.addContent(lastmodNode);
        url.addContent(loc);
        url.addContent(lastmod);
        
        urlset.addContent(url);
        
        urlset.addContent(urls);
        
        doc.addContent(urlset);
        XMLOutputter outputter = new XMLOutputter(FORMAT);
        return outputter.outputString(doc);
    }

    private Element getPostUrlElement(Post post, String siteAddress) {
        Element url = new Element(URL, NAMESPACE);
        Element loc = new Element(LOC, NAMESPACE);
        
        // For test
        /*Element name = new Element("name", NAMESPACE);
        name.addContent(new Text(post.getPostTitle()));
        url.addContent(name);*/
        
        Element lastmod = new Element(LASTMOD, NAMESPACE);
        Text locNode = new Text(
                String.format("%s%s", siteAddress, post.getLink()));
        loc.addContent(locNode);
        
        LocalDateTime updateTime = post.getUpdateTime();
        // getLatestComment
        
        Comment comment = commentService.getOne(new QueryWrapper<Comment>()
                .lambda()
                .eq(Comment::getPostId, post.getPostId())
                .eq(Comment::getCommentStatus, CommentStatusEnum.RELEASED)
                .eq(Comment::getIsDeleted, false)
                .orderByAsc(Comment::getCreateTime).last("LIMIT 1"));
        
        if (comment != null && updateTime.isBefore(comment.getCreateTime())) {
            updateTime = comment.getCreateTime();
        }
        
        Text lastmodNode = new Text(
                DateUtils.format(updateTime, DATE_FORMAT));
        lastmod.addContent(lastmodNode);
        url.addContent(loc);
        url.addContent(lastmod);
        return url;
    }
    
    private static Element getMetaUrlElement(Meta meta, String siteAddress) {
        Element url = new Element(URL, NAMESPACE);
        Element loc = new Element(LOC, NAMESPACE);
        Element lastmod = new Element(LASTMOD, NAMESPACE);
        Text locNode = new Text(
                String.format("%s%s%s", siteAddress, meta.getType() == MetaTypeEnum.TAG ? TAG_PATH : CATEGORY_PATH, 
                        UriUtils.encode(meta.getFriendlyName(), StandardCharsets.UTF_8)));
        loc.addContent(locNode);
        Text lastmodNode = new Text(
                DateUtils.format(meta.getUpdateTime(), DATE_FORMAT));
        lastmod.addContent(lastmodNode);
        url.addContent(loc);
        url.addContent(lastmod);
        return url;
    }
}
