package cn.reghao.tnb.search.esapp.blog.service;

import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.tnb.search.esapp.blog.db.repository.AboutViewRepository;
import cn.reghao.tnb.search.esapp.blog.db.repository.ArticleRepository;
import cn.reghao.tnb.search.esapp.blog.db.repository.ArticleTagRepository;
import cn.reghao.tnb.search.esapp.blog.db.repository.CategoryRepository;
import cn.reghao.tnb.search.esapp.blog.model.ArticleType;
import cn.reghao.tnb.search.esapp.blog.model.CategoryType;
import cn.reghao.tnb.search.esapp.blog.model.po.AboutView;
import cn.reghao.tnb.search.esapp.blog.model.po.Article;
import cn.reghao.tnb.search.esapp.blog.model.po.ArticleTag;
import cn.reghao.tnb.search.esapp.blog.model.po.Category;
import cn.reghao.tnb.search.esapp.blog.model.vo.*;
import cn.reghao.tnb.search.esapp.blog.model.vo.*;
import jakarta.persistence.criteria.Predicate;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2023-04-15 03:47:50
 */
@Service
public class ArticleQuery {
    private final ArticleRepository articleRepository;
    private final ArticleTagRepository articleTagRepository;
    private final CategoryRepository categoryRepository;
    private final ArticleViewService articleViewService;
    private final AboutViewRepository aboutViewRepository;

    public ArticleQuery(ArticleRepository articleRepository, ArticleTagRepository articleTagRepository,
                        CategoryRepository categoryRepository, ArticleViewService articleViewService,
                        AboutViewRepository aboutViewRepository) {
        this.articleRepository = articleRepository;
        this.articleTagRepository = articleTagRepository;
        this.categoryRepository = categoryRepository;
        this.articleViewService = articleViewService;
        this.aboutViewRepository = aboutViewRepository;
    }

    public EditArticle getEditArticle(String articleId) {
        Article article = articleRepository.findByArticleId(articleId);
        if (article == null) {
            return null;
        }

        List<Integer> tagIds = articleTagRepository.findByArticleId(articleId).stream()
                .map(ArticleTag::getTagId)
                .collect(Collectors.toList());
        List<String> tags = categoryRepository.findAllById(tagIds).stream()
                .map(Category::getName)
                .collect(Collectors.toList());
        return new EditArticle(article, tags);
    }

    @Cacheable(cacheNames = "ArticleVOs", key = "#articleId", unless = "#result == null")
    public ArticleVO getArticle(String articleId) {
        Article article = articleRepository.findByArticleId(articleId);
        if (article == null || !article.getPublished()) {
            return null;
        }

        int categoryId = article.getCategoryId();
        Category category = categoryRepository.findById(categoryId).orElse(null);
        String categoryName = category.getName();
        List<ArticleTag> list = articleTagRepository.findByArticleId(articleId);
        List<String> tags = list.stream()
                .map(articleTag -> {
                    int tagId = articleTag.getTagId();
                    Category category1 = categoryRepository.findById(tagId).orElse(null);
                    return category1 != null ? category1.getName() : null;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        int viewCount = articleViewService.getViewCount(articleId);
        ArticleVO articleVO = new ArticleVO(article, viewCount, categoryName, tags);
        int id = articleVO.getId();
        ArticleLink prev = getPrevLink(id);
        ArticleLink next = getNextLink(id);
        articleVO.setPrev(prev);
        articleVO.setNext(next);
        /*if ("markdown".equals(articleVO.getEditor())) {
            String html = MarkdownUtil.getHtml(articleVO.getContent());
            articleVO.setContent(html);
        }*/

        String content = articleVO.getContent();
        //content += "<br/><p class=\"copyright\">声明：本文归作者所有，未经作者允许，不得转载</p>";
        articleVO.setContent(content);
        return articleVO;
    }

    public ArticleLink getPrevLink(int id) {
        Specification<Article> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.lt(root.get("id"), id));

            return cb.and(predicates.toArray(new Predicate[0]));
        };

        List<Article> list = articleRepository.findAll(specification);
        return list.isEmpty() ? new ArticleLink() : getLink(list.get(0));
    }

    public ArticleLink getNextLink(int id) {
        Specification<Article> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.gt(root.get("id"), id));
            return cb.and(predicates.toArray(new Predicate[0]));
        };

        List<Article> list = articleRepository.findAll(specification);
        return list.isEmpty() ? new ArticleLink() : getLink(list.get(0));
    }

    public List<ArchiveArticle> getArchiveArticles() {
        List<ArticleLink> list = articleRepository.findAll().stream().map(this::getLink).collect(Collectors.toList());
        Map<String, ArchiveArticle> results = new LinkedHashMap<>();
        for (ArticleLink link : list) {
            String pubDate = link.getPubDate();
            String dateStr = pubDate.split(" ")[0];
            String yearMonthStr = dateStr.substring(0, dateStr.lastIndexOf("-"));
            ArchiveArticle article = results.computeIfAbsent(yearMonthStr, k -> new ArchiveArticle(yearMonthStr));
            article.getList().add(link);
        }

        return new ArrayList<>(results.values());
    }

    public List<ArticleLink> getArticleLinks() {
        int pageNumber = 1;
        int pageSize = 1000;
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(pageNumber-1, pageSize, sort);
        Page<Article> page = articleRepository.findByPublishedIsTrueAndPostType(ArticleType.Article.getValue(), pageable);
        return page.getContent().stream().map(this::getLink).collect(Collectors.toList());
    }

    public List<ArticleLink> getLatest(int size) {
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(0, size, sort);
        return articleRepository.findAll(pageable).stream()
                .map(this::getLink)
                .collect(Collectors.toList());
    }

    public List<ArticleLink> getHottest(int size) {
        List<ArticleCount> list = articleViewService.getArticleCount(size);
        if (list.isEmpty()) {
            return Collections.emptyList();
        }

        return list.stream()
                .map(articleCount -> articleRepository.findByArticleId(articleCount.getArticleId()))
                .filter(Objects::nonNull)
                .map(this::getLink)
                .collect(Collectors.toList());

        /*Specification<Article> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(root.get("articleId").in(articleIds));
            return cb.and(predicates.toArray(new Predicate[0]));
        };*/
    }

    private ArticleLink getLink(Article article) {
        String title = article.getTitle();
        String articleId = article.getArticleId();
        LocalDateTime pubDate = article.getPublishAt();
        return new ArticleLink(articleId, title, DateTimeConverter.format(pubDate));
    }

    public Page<UserArticle> findUserArticleByPage(int pageSize, int pageNumber) {
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(pageNumber-1, pageSize, sort);
        Page<Article> page = articleRepository.findByPublishedIsTrueAndPostType(ArticleType.Article.getValue(), pageable);

        List<UserArticle> list1 = page.getContent().stream().map(article -> {
            int categoryId1 = article.getCategoryId();
            Category category = categoryRepository.findById(categoryId1).orElse(null);
            int viewCount = articleViewService.getViewCount(article.getArticleId());
            String categoryName = category.getName();
            return new UserArticle(article, viewCount, categoryName);
        }).collect(Collectors.toList());
        return new PageImpl<>(list1, pageable, page.getTotalElements());
    }

    public Page<UserArticle> findByCategory(int pageSize, int pageNumber, String categoryName, int type) {
        Category category = categoryRepository.findByTypeAndName(type, categoryName);
        if (category == null) {
            return Page.empty();
        }

        long total;
        List<Article> list;
        List<UserArticle> list1;
        Sort sort = Sort.by(Sort.Direction.DESC, "createTime");
        Pageable pageable = PageRequest.of(pageNumber-1, pageSize, sort);
        if (type == CategoryType.Category.getValue()) {
            int categoryId = category.getId();
            int postType = ArticleType.Article.getValue();
            Page<Article> page = articleRepository.findByCategoryIdAndPostType(categoryId, postType, pageable);
            total = page.getTotalElements();
            list1 = page.getContent().stream()
                    .map(article -> {
                        int viewCount = articleViewService.getViewCount(article.getArticleId());
                        return new UserArticle(article, viewCount, categoryName);
                    })
                    .collect(Collectors.toList());
        } else {
            int tagId = category.getId();
            List<ArticleTag> articleTags = articleTagRepository.findByTagId(tagId);
            total = articleTags.size();

            list = findByArticleIds(articleTags.stream().map(ArticleTag::getArticleId).collect(Collectors.toList()));
            list1 = list.stream()
                    .map(article -> {
                        int categoryId = article.getCategoryId();
                        Category category1 = categoryRepository.findById(categoryId).orElse(null);
                        int viewCount = articleViewService.getViewCount(article.getArticleId());
                        return new UserArticle(article, viewCount, category1.getName());
                    })
                    .collect(Collectors.toList());
        }

        return new PageImpl<>(list1, pageable, total);
    }

    public Page<AdminArticle> findAdminArticleByPage(int pageSize, int pageNumber, int postType, int categoryId, int published, String title) {
        Specification<Article> specification = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (!title.isBlank()) {
                predicates.add(cb.like(root.get("title"), "%"+title+"%"));
            }

            if (categoryId != 0) {
                predicates.add(cb.equal(root.get("categoryId"), categoryId));
            }

            if (published == 1) {
                predicates.add(cb.equal(root.get("published"), true));
            } else if (published == 2) {
                predicates.add(cb.equal(root.get("published"), false));
            }

            if (postType != 0) {
                predicates.add(cb.equal(root.get("postType"), postType));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };

        String domain = "";
        Pageable pageable = PageRequest.of(pageNumber-1, pageSize, Sort.by(Sort.Direction.DESC, "createTime"));
        Page<Article> page = articleRepository.findAll(specification, pageable);
        List<AdminArticle> list1 = page.stream().map(article -> {
            String articleId = article.getArticleId();
            List<Integer> tagIds = articleTagRepository.findByArticleId(articleId).stream()
                    .map(ArticleTag::getTagId)
                    .collect(Collectors.toList());

            List<String> tags = categoryRepository.findAllById(tagIds).stream()
                    .map(Category::getName)
                    .collect(Collectors.toList());

            int categoryId1 = article.getCategoryId();
            Category category = categoryRepository.findById(categoryId1).orElse(null);
            String name = category.getName();
            int viewCount = articleViewService.getViewCount(articleId);
            return new AdminArticle(article, viewCount, name, tags, domain);
        }).collect(Collectors.toList());
        return new PageImpl<>(list1, pageable, page.getTotalElements());
    }

    public List<Article> findByArticleIds(List<String> articleIds) {
        List<Article> list = new ArrayList<>();
        for (String articleId : articleIds) {
            list.add(articleRepository.findByArticleId(articleId));
        }

        return list;
    }

    public AboutView getAboutView() {
        return aboutViewRepository.findById(1).orElse(null);
    }
}
