package com.cclotus.cms.service

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.cclotus.cms.domain.*
import com.cclotus.cms.mapper.CmsPostAttachmentMapper
import com.cclotus.cms.mapper.CmsPostMapper
import com.cclotus.cms.mapper.CmsPostMetaMapper
import com.cclotus.cms.mapper.CmsTermRelationshipsMapper
import com.cclotus.cms.util.EntityPathVariable
import com.cclotus.cms.vo.CmsPostFormReq
import com.cclotus.cms.vo.CmsPostFormRes
import com.ruoyi.common.core.page.TableDataInfo
import com.ruoyi.common.exception.ServiceException
import com.ruoyi.common.reqParam.PageParam
import com.ruoyi.common.utils.PageUtils
import com.ruoyi.common.utils.TableDataInfoUtil
import org.springframework.beans.BeanUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class CmsPostService {

    @Autowired
    lateinit var cmsTermService: CmsTermService

    @Autowired
    lateinit var cmsPostMapper: CmsPostMapper

    @Autowired
    lateinit var cmsTermRelationshipsMapper: CmsTermRelationshipsMapper

    @Autowired
    lateinit var cmsPostMetaMapper: CmsPostMetaMapper

    @Autowired
    lateinit var cmsPostMetaService: CmsPostMetaService

    @Autowired
    lateinit var cmsTermMetaService: CmsTermMetaService

    @Autowired
    lateinit var cmsPostAttachmentMapper: CmsPostAttachmentMapper

    /**
     * 查询内容列表
     * @param termAlias 分类别名
     * @param deep      是否显示栏目下子栏目的文章，默认不查询子栏目下的文章
     * @param post      过滤条件
     * @param pageParam 分页条件
     * @param content   列表页是否返回内容详情
     * @param meta      扩展属性过滤条件
     */
    fun list(
        termAlias: String?,
        deep: Boolean,
        post: CmsPost,
        pageParam: PageParam?,
        content: Boolean,
        meta: Map<String, String>? = null,
    ): TableDataInfo {

        // 如果别名不为空，查询符合条件的栏目信息
        val terms = termAlias?.takeIf { it.isNotBlank() }?.let {
            cmsTermService.getTermsByAlias(termAlias, deep)
        }

        // 如果存在termAlias过滤条件，先查询与该termAlias关联的objectIds
        val objectIds = terms?.let {
            cmsTermService.getObjectIdsByTerms(it).also { ids ->
                if (ids.isEmpty()) {
                    return TableDataInfoUtil.getDataTable(emptyList<CmsPost>())
                }
            }
        }

        // 如果存在扩展属性过滤条件，查询内容元数据表
        val postIdsByMeta = if (!meta.isNullOrEmpty()) {
            cmsPostMetaMapper.selectPostMetaList(meta)
        } else null

        val queryWrapper = QueryWrapper<CmsPost>()
        if (!content) {
            // content为false时，不返回content字段，避免返回的数据量过大
            queryWrapper.select(CmsPost::class.java) { info -> !info.column.equals("content") }
        }

        // 审核状态字段条件不为空时，判断所属栏目是否存在需要审核扩展属性，如果需要审核，添加审核过滤条件
        if (!post.review.isNullOrBlank() && !deep) {
            terms?.getOrNull(0)?.let {
                // 查询栏目是否存在需要审核属性，并且该值为需要审核
                cmsTermMetaService.selectListByTermIds(listOf(it.termId!!))
                    .filter { meta -> meta.metaKey == "reviewRequired" && meta.metaValue == "1" }.getOrNull(0)?.run {
                        queryWrapper.eq("review", post.review)
                    }
            }
        }

        if (!objectIds.isNullOrEmpty()) {
            // 设置termAlias过滤条件时，只返回与alias关联的objects
            queryWrapper.`in`("post_id", objectIds)
        }

        if (!postIdsByMeta.isNullOrEmpty()) {
            // 过滤符合扩展字段查询条件的文章列表
            queryWrapper.`in`("post_id", postIdsByMeta)
        }

        queryWrapper.eq(!post.contentType.isNullOrBlank(), "content_type", post.contentType)
            .like(!post.title.isNullOrBlank(), "title", post.title)
            .like(!post.summary.isNullOrBlank(), "summary", post.summary)
            .eq(!post.status.isNullOrBlank(), "status", post.status)
            .eq(post.topStatus != null, "top_status", post.topStatus)
            .eq(!post.visible.isNullOrBlank(), "visible", post.visible)
            .eq(!post.createBy.isNullOrBlank(), "create_by", post.createBy).orderByDesc("top_status", "create_time")

        // 查询post主表，并处理分页
        pageParam?.let {
            it.orderByColumn = null
            PageUtils.startPage(it)
        }
        val posts = cmsPostMapper.selectList(queryWrapper).also {
            if (it.isEmpty()) return TableDataInfoUtil.getDataTable(it)
        }

        // 查询meta从表，并按postId分组
        val postIds = posts.map { it.postId!! }.distinct()
        val metas = cmsPostMetaService.selectListByPostIds(postIds).groupBy { it.postId!! }

        // 将meta从表数据关联到post主表
        posts.forEach { it.meta = metas[it.postId] ?: emptyList() }

        // 构建分页查询结果，转换post并替换rows
        return TableDataInfoUtil.getDataTable(posts).apply {
            rows = posts.map { it.toCmsPostFormRes() }
        }
    }

    fun CmsPost.toCmsPostFormRes(): CmsPostFormRes {
        return CmsPostFormRes().apply {
            BeanUtils.copyProperties(this@toCmsPostFormRes, this)
            this.meta =
                this@toCmsPostFormRes.meta.associate { it.metaKey!! to it.metaValue } as MutableMap<String, String?>
        }
    }

    /**
     * 根据文章别名或Id获取文章详情
     */
    fun detail(contentType: String, post: String): CmsPostFormRes {
        val cmsPost = getPostByIdentity(contentType, post)
        return cmsPost.toCmsPostFormRes()
    }

    /**
     * 根据文章ID获取文章详情
     */
    fun getPostInfoEx(
        catalogAlias: String?,
        postType: String,
        postId: Int?,
        postAlias: String?,
    ): CmsPostFormRes {
        if (postId == null && postAlias == null) {
            throw ServiceException("文章ID和别名不可同时为空", 500)
        }

        val cmsPost = cmsPostMapper.selectPostEx(catalogAlias, postType, postId, postAlias)
            ?: throw ServiceException("指定的文章信息不存在", 500)

        return cmsPost.toCmsPostFormRes()
    }

    /**
     * 新增发布内容
     * @param post 发布内容
     * @param poster 发布者
     * @return 影响数据条数
     */
    @Transactional
    fun create(post: CmsPostFormReq, poster: String): Int {

        post.alias?.let {
            if (it.isNotBlank()) {
                val queryWrapper = QueryWrapper<CmsPost>().eq("alias", it)
                if (cmsPostMapper.selectCount(queryWrapper) > 0) throw ServiceException("页面别名已存在", 500)
            }
        }

        // 插入发布内容
        val cmsPost = CmsPost().apply {
            BeanUtils.copyProperties(post, this)
            this.contentType = this.contentType ?: "article"
            this.status = this.status ?: "0"
            this.topStatus = this.topStatus ?: 0
            this.visible = this.visible ?: "1"
            this.createBy = poster
        }.also {
            if (cmsPostMapper.insert(it) != 1) {
                throw ServiceException("插入发布内容失败", 500)
            }
        }

        // 插入发布内容与栏目的关联关系
        post.catalogIds?.forEach { catalogId ->
            CmsTermRelationships().apply {
                this.objectId = cmsPost.postId
                this.termId = catalogId
            }.also {
                if (cmsTermRelationshipsMapper.insert(it) != 1) {
                    throw ServiceException("插入发布内容与栏目的关联关系失败", 500)
                }
            }
        }

        // 插入发布内容的扩展属性键值列表
        post.meta?.forEach { meta ->
            CmsPostMeta().apply {
                this.postId = cmsPost.postId
                this.metaKey = meta.key
                this.metaValue = meta.value
            }.also {
                if (cmsPostMetaMapper.insert(it) != 1) {
                    throw ServiceException("插入发布内容的扩展属性列表失败", 500)
                }
            }
        }

        // 插入内容附件信息
        post.urls?.split(",")?.forEach { url ->
            CmsPostAttachment().apply {
                this.url = url
                this.postId = cmsPost.postId
            }.also {
                if (cmsPostAttachmentMapper.insert(it) != 1) {
                    throw ServiceException("插入内容附件失败", 500)
                }
            }
        }

        return 1
    }

    /**
     * 在指定的类型下创建内容
     *
     * @param post 内容详情
     * @param poster 创建者
     * @param termAlias 类型别名
     */
    @Transactional
    fun createByTermAlias(post: CmsPostFormReq, poster: String, termAlias: String): Int {
        val cmsTerm =
            cmsTermService.getTermsByAlias(termAlias, false).getOrNull(0) ?: throw ServiceException("指定类型不存在")
        val termId = cmsTerm.termId
        post.catalogIds = post.catalogIds?.also {
            if (!it.contains(termId)) it.add(termId!!)
        } ?: mutableListOf(termId!!)
        return create(post, poster)
    }

    /**
     * 更新发布内容
     * @param post 待修改发布内容
     * @return 影响数据条数
     */
    @Transactional
    fun update(post: CmsPostFormReq, poster: String): Int {
        val postId = requireNotNull(post.postId) { "内容Id不可为空" }

        // 获取待更新发布内容
        val entity = cmsPostMapper.selectById(postId) ?: throw ServiceException("文章不存在", 500)

        post.alias?.let {
            if (it.isNotBlank()) {
                val queryWrapper = QueryWrapper<CmsPost>().eq("alias", it).ne("post_id", entity.postId)
                if (cmsPostMapper.selectCount(queryWrapper) > 0) throw ServiceException("页面别名已存在", 500)
            }
        }

        // 更新发布内容与栏目类别的关联关系
        post.catalogIds?.let {
            updatePostCatalogsRelations(postId, it)
        }

        // 更新发布内容的扩展属性键值列表
        post.meta?.let {
            updatePostMetas(postId, it)
        }

        // 更新内容附件列表
        post.urls?.split(",")?.let {
            updataPostAttachments(postId, it)
        }

        // 更新发布内容
        entity.apply {
            BeanUtils.copyProperties(post, this, "origin")
            this.updateBy = poster
        }.also {
            cmsPostMapper.updateById(it)
        }

        return 1
    }

    /**
     * 根据IDs删除发布内容
     */
    @Transactional
    fun deleteByIds(postIds: Array<Int>): Int {
        if (postIds.isEmpty()) return 0
        // 删除指定内容的配置信息
        cmsPostMetaMapper.delete(QueryWrapper<CmsPostMeta>().`in`("post_id", postIds.toList()))
        // 删除内容与栏目的关联
        cmsTermRelationshipsMapper.deleteObjectIds(postIds.toList())
        // 删除内容的附件信息
        cmsPostAttachmentMapper.delete(QueryWrapper<CmsPostAttachment>().`in`("post_id", postIds.toList()))

        return cmsPostMapper.deleteBatchIds(postIds.toList())
    }

    /**
     * 审核内容
     *
     * @param contentType 内容类型
     * @param post 内容标识，文章ID或者别名
     * @param reviewForm 审核信息
     */
    fun review(contentType: String, post: String, reviewForm: CmsReviewForm) {
        val cmsPost = getPostByIdentity(contentType, post)
        cmsPost.review = reviewForm.review
        cmsPost.comment = reviewForm.comment
        cmsPostMapper.updateById(cmsPost)
    }

    /**
     * 根据标识获取内容信息
     *
     * @param contentType 内容类型
     * @param identity 内容标识，ID或别名
     * @return 内容详情
     */
    private fun getPostByIdentity(contentType: String, identity: String): CmsPost {
        return EntityPathVariable.parse(identity).let {
            if (it.id != null) {
                cmsPostMapper.selectPostByPostId(contentType, it.id) ?: throw ServiceException(
                    "指定ID文章信息不存在", 500
                )
            } else if (it.alias != null) {
                cmsPostMapper.selectPostByAlias(contentType, it.alias)
                    ?: throw ServiceException("指定别名的文章信息不存在", 500)
            } else {
                throw ServiceException("指定的文章信息不存在", 500)
            }
        }
    }

    /**
     * 更新发布内容的扩展属性键值列表
     */
    private fun updatePostMetas(postId: Int, metas: Map<String, String>) {
        // 删除旧的属性
        cmsPostMetaMapper.delete(QueryWrapper<CmsPostMeta>().eq("post_id", postId))

        // 插入新的属性
        metas.forEach { meta ->
            CmsPostMeta().apply {
                this.postId = postId
                this.metaKey = meta.key
                this.metaValue = meta.value
            }.also {
                if (cmsPostMetaMapper.insert(it) != 1) {
                    throw ServiceException("插入发布内容的扩展属性列表失败", 500)
                }
            }
        }
    }

    /**
     * 更新发布内容与栏目类别的关联关系
     */
    private fun updatePostCatalogsRelations(postId: Int, catalogIds: List<Int>) {

        // 获取关联的栏目类型
        val rela = cmsTermRelationshipsMapper.selectCatalogRelationsByPostId(postId)
        val relaCatalogIds = rela.map { r -> r.termId }.toList()

        // 计算待删除栏目类别列表，和待插入栏目类别列表
        val removeIds = relaCatalogIds.minus(catalogIds.toSet())
        val addIds = catalogIds.minus(relaCatalogIds.toSet())

        // 删除旧的关联关系
        rela.forEach {
            if (removeIds.contains(it.termId)) {
                cmsTermRelationshipsMapper.deleteById(it.relaId)
            }
        }

        // 插入新的关联关系
        //  TODO: 需要检查新增的栏目类别是否合法
        catalogIds.forEach {
            if (addIds.contains(it)) {
                CmsTermRelationships().apply {
                    this.objectId = postId
                    this.termId = it
                }.also { r ->
                    cmsTermRelationshipsMapper.insert(r)
                }
            }
        }
    }

    // 更新内容附件列表
    private fun updataPostAttachments(postId: Int, urls: List<String>) {
        // 获取内容附件列表
        val attachments = cmsPostAttachmentMapper.selectList(
            KtQueryWrapper(CmsPostAttachment()).eq(
                CmsPostAttachment::postId, postId
            )
        )
        val attachmentsUrls = attachments.map { it.url }.toList()

        // 计算待删除栏目类别列表，和待插入栏目类别列表
        val deleteList = mutableListOf<CmsPostAttachment>()
        val addList = mutableListOf<CmsPostAttachment>()
        attachments.forEach {
            if (!urls.contains(it.url)) {
                deleteList.add(it)
            }
        }
        urls.forEach {
            if (!attachmentsUrls.contains(it)) {
                addList.add(CmsPostAttachment().apply {
                    this.postId = postId
                    this.url = it
                })
            }
        }

        addList.forEach {
            cmsPostAttachmentMapper.insert(it)
        }

        deleteList.map { it.attachmentId }.run {
            cmsPostAttachmentMapper.deleteBatchIds(this)
        }


    }

}
