package me.qwq.doghouse.controller.admin.post;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.component.PostTypeComponent;
import me.qwq.doghouse.controller.common.BaseController;
import me.qwq.doghouse.entity.Asset;
import me.qwq.doghouse.entity.Cate;
import me.qwq.doghouse.entity.Meta;
import me.qwq.doghouse.entity.Tag;
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.interfaces.IPostTypeConfig;
import me.qwq.doghouse.pojo.dto.LayPageReq;
import me.qwq.doghouse.pojo.dto.LayPageResp;
import me.qwq.doghouse.pojo.dto.R;
import me.qwq.doghouse.service.AssetService;
import me.qwq.doghouse.service.MetaService;
import me.qwq.doghouse.service.RelationshipService;
import me.qwq.doghouse.service.post.AbstractPostService;
import me.qwq.doghouse.util.SpringContextHolder;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;


/**
 * Post 后台控制器
 * @author Doghole
 */
@Controller
@Slf4j
public abstract class AbstractAdminPostController<C extends IPostTypeConfig<C>, S extends AbstractPostService<S, C>> extends BaseController {

    @Autowired
    AssetService assetService;
    
    @Autowired
    MetaService metaService;
    
    @Autowired
    RelationshipService relationshipService;
    
    @Autowired
    PostTypeComponent postTypeComponent;
    
    @Autowired
    ObjectMapper objectMapper;

    C postTypeConfig;
    
    /** 
     * 注意本方法不可信，尤其是某些 PostTypeEnum 未重写本抽象类内的方法的时候。能用 postTypeComponent.getPostTypeConfig(postTypeEnum)
     * 来获取的时候尽量不用本方法，本方法仅在无法确认 postTypeEnum 的时候，默认其类型是继承了本抽象类的 PostTypeService 对应的类型
     * @return
     */
    C getPostTypeConfig() {
        if (postTypeConfig != null) return postTypeConfig;
        ResolvableType type = ResolvableType.forClass(this.getClass()).as(AbstractAdminPostController.class);
        ResolvableType iPageConfigType = type.getGeneric(0);
        @SuppressWarnings("unchecked")
        Class<C> iPageConfigClass = (Class<C>) iPageConfigType.resolve();
        postTypeConfig = SpringContextHolder.getBean(iPageConfigClass);
        return postTypeConfig;
    }
    
    S postService;
    
    /** 
     * 注意本方法不可信，尤其是某些 PostTypeEnum 未重写本抽象类内的方法的时候。能用 postTypeComponent.getPostTypeService(postTypeEnum)
     * 来获取的时候尽量不用本方法，本方法仅在无法确认 postTypeEnum 的时候，默认其类型是继承了本抽象类的 PostTypeService 对应的类型
     * @return
     */
    S getPostService() {
        if (postService != null) return postService;
        ResolvableType type = ResolvableType.forClass(this.getClass()).as(AbstractAdminPostController.class);
        ResolvableType postServiceType = type.getGeneric(1);
        @SuppressWarnings("unchecked")
        Class<S> postServiceClass = (Class<S>) postServiceType.resolve();
        postService = SpringContextHolder.getBean(postServiceClass);
        return postService;
    }
    
    @GetMapping({"", "index", "/index"})
    public String nonSlashIndex() {
        
        RequestMapping requestMapping = this.getClass().getAnnotation(RequestMapping.class);
        if (requestMapping == null || requestMapping.value().length == 0) {
            throw new PageNotFoundException();
        }
        String uri = requestMapping.value()[0];
        uri = uri.replaceAll("/+$", "");
        return "redirect:" + uri + "/";
    }
    
    /**
     * 列表界面
     */
    @GetMapping("/")
    public String index(
            @RequestParam(required = false) Long categoryId,
            @RequestParam(required = false) Long tagId) {
        
        PostTypeEnum postType = getPostTypeConfig().getPostType();
        
        request.setAttribute("title", String.format("%s管理", postType.getNote()));
        request.setAttribute("manageTitle", String.format("%s列表", postType.getNote()));
        if (categoryId != null) {
            Meta cate = metaService.getOne(new MPJLambdaWrapper<Meta>().eq(Meta::getMid, categoryId));
            request.setAttribute("title", String.format("分类 %s 下的%s管理", cate.getName(), postType.getNote()));
            request.setAttribute("manageTitle", String.format("分类 %s 下的%s列表", cate.getName(), postType.getNote()));
        }
        if (tagId != null) {
            Meta tag = metaService.getOne(new MPJLambdaWrapper<Meta>().eq(Meta::getMid, tagId));;
            request.setAttribute("title", String.format("标签 %s 下的%s管理", tag.getName(), postType.getNote()));
            request.setAttribute("manageTitle", String.format("标签 %s 下的%s列表", tag.getName(), postType.getNote()));
        }
        return "admin/" + postType.toLowerString() + "/index";
    }
    
    /**
     * 编辑
     * @throws JsonProcessingException 
     */
    @GetMapping("/edit")
    public String edit(@RequestParam(required = false) Long postId) throws JsonProcessingException {

        PostTypeEnum postType = getPostTypeConfig().getPostType();
        
        Post post = getPostService().getById(postId);
        if (post != null) {
            request.setAttribute("post", post);
            request.setAttribute("title", "编辑 - " + post.getPostTitle());
            List<Asset> assets = assetService.list(
                    new LambdaQueryWrapper<Asset>().eq(Asset::getFromPostId, postId));
            request.setAttribute("assets", StringEscapeUtils.escapeEcmaScript(objectMapper.writeValueAsString(assets)));
        }
        else {
            request.setAttribute("title", "新增" + postType.getNote());
        }
        return "admin/" + postType.toLowerString() + "/edit";
    }

    /**
     * 保存
     * @param post 文章实体模型
     * @param assets 包含的资产
     * @param tagsName 标签
     * @param categoriesId 分类
     * @param postType 文章类型 - 文章|页面|Wiki
     * @return
     */
    @ResponseBody
    @PostMapping("/edit")
    public R<?> savePost(@Valid Post post,
            BindingResult bindingResult, 
            @RequestParam(name="assets[]", required=false) Long[] assets,
            @RequestParam(name="_tagsName[]", required=false) String[] tagsName,
            @RequestParam(name="_categoriesId[]", required=false) Long[] categoriesId
            ) {
        
        if (bindingResult.hasErrors()) {
            throw new RException(HttpStatus.BAD_REQUEST, bindingResult.getAllErrors().get(0).getDefaultMessage());
        }
        
        if (ObjectUtils.isEmpty(post)) {
            throw RException.badRequest("无法识别的请求");
        }
        Post existsPost = post.getPostId() != null ? getPostService().getById(post.getPostId()) : null;
        Boolean isUpdate = existsPost != null;
        
        getPostService().beforeSaving(post, assets, tagsName, categoriesId, isUpdate);
        
        if (!getPostService().saveOrUpdate(post)) {
            throw RException.internalServerError("保存失败");
        }
        
        getPostService().afterSaving(post, assets, tagsName, categoriesId, isUpdate);
        
        Long postId = post.getPostId();
        post = getPostService().getById(postId);
        return R.ok(post);
    }

    /**
     * 
     * @param pageReq 分页请求模型
     * @param condition 查询条件
     * @param categoryId 分类 ID，可指定分类查询
     * @param tagId 标签 ID，可指定标签查询
     * @return Post 结果列表分页模型
     */
    @ResponseBody
    @GetMapping("/list")
    public LayPageResp<?> getPostList(
            LayPageReq<Post> pageReq, 
            Post condition, 
            Long categoryId, 
            Long tagId,
            String keywords) {
        
        MPJLambdaWrapper<Post> ew = getPostService().getPostCommonWrapper()
                // TODO
                //.likeOrEqNotNull(condition) // postType 以当前控制器为准
                .eq(Post::getType, getPostTypeConfig().getPostType())
                .eq(Post::getIsDeleted, false)
                .eq(Objects.nonNull(categoryId), Cate::getMid, categoryId)
                .eq(Objects.nonNull(tagId), Tag::getMid, tagId)
                .and(StringUtils.isNotBlank(keywords), o -> {
                    o.like(Post::getPostTitle, keywords)
                     .or().like(Post::getPostPreface, keywords)
                     .or().like(Post::getPostContent, keywords)
                     .or().like(Post::getKeywords, keywords);
                })
                .orderByDesc(Post::getCreateTime);
        IPage<Post> page = getPostService().page(new Page<>(pageReq.getPage(), pageReq.getLimit()), ew);
        
        LayPageResp<Post> result = new LayPageResp<>(page);
        return result;
    }
    

    /**
     * 对 Post 批量操作
     * @param ids 批量操作的 Post ID 集合
     * @param op 将要转换为的 PostStatusEnum
     * @param to 移动至（针对 Wiki）
     * @return
     */
    @ResponseBody
    @PostMapping("/batchOp")
    public R<?> batchOp(
            @RequestParam(value="ids[]", required=true)
            Long[] ids, PostStatusEnum postStatus) {
        
        if (Objects.isNull(ids) || ids.length == 0) {
            throw RException.badRequest("提供的批量操作 ID 不能为空");
        }
        List<Long> idArray = Arrays.asList(ids);
        LambdaQueryWrapper<Post> qw = new LambdaQueryWrapper<Post>().in(Post::getPostId, idArray);
        List<Post> waitForUpdates = postService.list(qw);
        
        // 看下这批的 postType 是否一致, 不一致直接不允许, 简化操作
        boolean allEqual = waitForUpdates.stream()
                .map(Post::getType)
                .distinct()
                .count() <= 1;
        if (!allEqual) {
            throw RException.badRequest("批量操作类型不一致");
        }
        
        LambdaUpdateWrapper<Post> uw =
                new LambdaUpdateWrapper<Post>()
                .in(Post::getPostId, idArray);
        
        boolean delete = Objects.isNull(postStatus);
        // 逻辑删除
        uw.set(delete, Post::getIsDeleted, true);
        // 更改 Post 状态
        uw.set(!delete, Post::getPostStatus, postStatus);
        uw.set(!delete && postStatus != PostStatusEnum.RELEASED, Post::getSearchable, false);
        return R.judge(
                getPostService().update(uw), 
                "批量操作保存失败");
    }

    /**
     * 删除文章
     * @param postId
     * @return
     */
    @ResponseBody
    @PostMapping("/delete")
    public R<?> delete(@RequestParam Long postId) {
        Post post = postService.getById(postId);
        if (post == null || post.getIsDeleted()) {
            throw RException.badRequest("删除失败, 请检查是否已删除或不存在");
        }
        AbstractPostService<?, ?> postTypeService = postTypeComponent.getPostTypeService(post.getType());
        LambdaUpdateWrapper<Post> uw = new LambdaUpdateWrapper<Post>()
                .eq(Post::getPostId, postId)
                .set(Post::getIsDeleted, true)
                .set(Post::getUpdateTime, LocalDateTime.now());
        return R.judge(postTypeService.update(uw));
    }

}
