package com.ssyf.module.resource.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssyf.common.core.util.AssertUtil;
import com.ssyf.common.core.util.EntityUtil;
import com.ssyf.common.core.util.SimpleLockerUtil;
import com.ssyf.module.resource.domain.entity.Filesystem;
import com.ssyf.module.resource.domain.query.FilesystemQuery;
import com.ssyf.module.resource.domain.query.MoveSortQuery;
import com.ssyf.module.resource.enums.FilesystemTypeEnum;
import com.ssyf.module.resource.mapper.FilesystemMapper;
import com.ssyf.module.resource.service.FilesystemService;

import com.ssyf.plugs.mysql.helper.MybatisPlusHelper;
import com.ssyf.plugs.pedigree.DataPedigreeHelper;
import com.ssyf.plugs.pedigree.TreeCascadeBuilder;
import com.ssyf.common.security.domain.ClientInfo;
import com.ssyf.common.security.helper.ClientContextHelper;
import com.ssyf.common.web.apicode.ApiCodeUser;
import com.ssyf.common.web.error.ServiceRuntimeException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.Lock;

/**
 * 文档分组实现类
 *
 * @author huwei
 * @date 2024/4/18 14:27
 */
@Service
public class FilesystemServiceImpl extends ServiceImpl<FilesystemMapper, Filesystem> implements FilesystemService {

    private String getAddLockKey(String hostId) {
        return Filesystem.class.getSimpleName() + "-Add-" + hostId;
    }

    /**
     * 添加一个新的文档目录。
     * 实现：
     * 1. 新增文档目录；
     * 2. 创建文档目录与宿主的关联
     *
     * @param query 查询对象，包含创建文档目录所需的信息。
     * @return 返回创建的文档目录对象。
     * @Transactional 注解指明该方法是一个事务方法，任何异常都将回滚事务。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Filesystem addOne(FilesystemQuery query) {
        // 获取当前客户端信息
        ClientInfo clientInfo = ClientContextHelper.getClientInfo();
        // 将查询对象转换为文档目录对象
        Filesystem filesystem = BeanUtil.toBean(query, Filesystem.class);
        // 设置文档目录的创建者信息和创建时间
        filesystem.setCreator(clientInfo.getNickname()).setCreatorUid(clientInfo.getId().toString()).setCreatedTime(LocalDateTime.now());
        // 生成并设置文档目录的唯一标识
        filesystem.setUid(IdUtil.simpleUUID());
        //设置默认类型
        filesystem.setType(filesystem.getType() == null ? FilesystemTypeEnum.dir.getValue() : filesystem.getType());
        String lockKey = StrUtil.isBlank(query.getParentId()) ? getAddLockKey(query.getHostId()) : query.getParentId();
        Filesystem parent = StrUtil.isBlank(query.getParentId()) ? null : this.getByUid(query.getParentId());
        Lock lock = SimpleLockerUtil.getLocker(lockKey, Filesystem.class.getSimpleName());
        lock.lock();
        try {
            //设置级联信息
            Filesystem lastChild = this.getLastFormNextChildrenByUid(query.getParentId());
            DataPedigreeHelper.initCascadeField(filesystem, query.getParentId(), parent, lastChild);
            // 保存文档目录
            this.save(filesystem);
        } finally {
            lock.unlock();
        }
        return filesystem;
    }

    /**
     * 根据UID更新文档目录信息。
     * 实现：
     * 1. 检查权限
     * 2. 更新
     *
     * @param uid   用户唯一标识符，用于确定要更新的文档目录。
     * @param query 包含更新内容的查询对象，通过该对象的属性更新对应的文档目录信息。
     */
    @Override
    public void updateByUid(String uid, FilesystemQuery query) {
        // 检查权限
        checkPermission(uid);

        Filesystem filesystem = this.getByUid(uid);
        AssertUtil.isNotNull(filesystem, ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));
        //文档类型，parentId不能修改
        if(query.getType() != null && !query.getType().equals(filesystem.getType())){
            ServiceRuntimeException.throw0(ApiCodeUser.PARAMETER_ERROR.getCode() ,"文档类型不能修改");
        }
        query.setParentId(null);

        // 将查询对象转换为文档目录对象并设置UID，准备更新
        filesystem = BeanUtil.toBean(query, Filesystem.class);
        filesystem.setUid(uid);

        // 构建查询条件，用于指定更新操作影响的记录
        LambdaQueryWrapper<Filesystem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Filesystem::getUid, uid);
        // 执行更新操作
        this.update(filesystem, queryWrapper);
    }


    /**
     * 根据UID删除文档。
     * 实现：
     * 1. 检查权限
     * 2. 获取目录的所有子目录信息
     * 3. 删除目录、子目录与宿主的关联
     * 4. 删除目录、子目录与文档的关联
     * 5. 删除目录、子目录
     *
     * @param uid 文档的唯一标识符。
     * @throws ServiceRuntimeException 如果尝试删除仍在使用的文档或检查权限失败时抛出。
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void removeByUid(String uid) {
        // 检查用户权限
        Filesystem filesystem = checkPermission(uid);
        List<Filesystem> children = this.getAllChildren(filesystem);
        List<String> uids = EntityUtil.extractProperty(children, Filesystem::getUid);
        uids.add(uid);

        //删除自己和所有子数据
        LambdaQueryWrapper<Filesystem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Filesystem::getUid, uids);
        this.remove(queryWrapper);
    }


    /**
     * 获取指定uid的数据
     *
     * @param uid
     * @return
     */
    @Override
    public Filesystem getByUid(String uid) {
        return getOne(new LambdaQueryWrapper<Filesystem>().eq(Filesystem::getUid, uid));
    }

    /**
     * 分页获取数据
     *
     * @param query
     * @return
     */
    @Override
    public PageInfo<Filesystem> getPage(FilesystemQuery query) {
        //分页
        PageHelper.startPage(query.getPageNum(), query.getPageSize());
        List<Filesystem> list = this.getAll(query);
        return new PageInfo<>(list);
    }

    /**
     * 获取指定主机ID和查询条件的所有文档目录。
     *
     * @param query 查询条件，用于进一步筛选文档目录。
     * @return 返回符合条件的文档目录列表。如果不存在匹配的文档目录，则返回空列表。
     */
    @Override
    public List<Filesystem> getAll(FilesystemQuery query) {
        // 根据查询条件创建查询包装器，并加入UID的条件限制
        LambdaQueryWrapper<Filesystem> queryWrapper = createBaseWrapper(query);
        // 执行查询并返回结果
        return list(queryWrapper);
    }

    @Override
    public List<Filesystem> getNextChildrenByUid(String uid, FilesystemQuery query) {
        return list(createNextChildrenWrapper(uid, query));
    }

    @Override
    public List<Filesystem> getAllChildrenByUid(String uid, FilesystemQuery query) {
        Filesystem parent = this.getByUid(uid);
        //判断父级是否存在
        AssertUtil.isNotNull(parent, ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));
        //设置空，防止createBaseWrapper()设置parentId查询
        query.setParentId(null);
        LambdaQueryWrapper<Filesystem> queryWrapper = this.createBaseWrapper(query);
        queryWrapper.eq(Filesystem::getFamilyId, parent.getFamilyId());
        queryWrapper.like(Filesystem::getPedigree, parent.getPedigree())
                .ne(Filesystem::getPedigree, parent.getPedigree());
        return list(queryWrapper);
    }

    @Override
    public PageInfo<Filesystem> getPageNextChildrenByUid(String uid, FilesystemQuery query) {
        PageHelper.startPage(query.getPageNum() ,query.getPageSize());
        return new PageInfo<>(getNextChildrenByUid(uid ,query));
    }

    @Override
    public PageInfo<Filesystem> getPageAllChildrenByUid(String uid, FilesystemQuery query) {
        PageHelper.startPage(query.getPageNum() ,query.getPageSize());
        return new PageInfo<>(getAllChildrenByUid(uid ,query));
    }

    /**
     * 检查用户是否有权限访问文档。
     *
     * @param uid 用户的唯一标识符。
     * @throws ServiceRuntimeException 如果文档目录为null或当前用户不是文档的创建者，则抛出此异常。
     */
    @Override
    public Filesystem checkPermission(String uid) {
        // 通过用户ID获取对应的文档目录
        Filesystem filesystem = this.getByUid(uid);
        // 确保文档目录不为null，否则抛出数据不存在的异常
        AssertUtil.isNotNull(filesystem, ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));

        // 获取当前客户端的信息
        ClientInfo clientInfo = ClientContextHelper.getClientInfo();
        // 获取当前用户的UID
        String currentUserUid = clientInfo.getId().toString();
        // 确保只有文档的创建者才有权限访问，否则抛出无权限访问的异常
        AssertUtil.isTrue(filesystem.getCreatorUid().equals(currentUserUid), ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));

        return filesystem;
    }

    @Override
    public Filesystem getLastFormNextChildrenByUid(String uid) {
        LambdaQueryWrapper<Filesystem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Filesystem::getParentId, uid).orderByDesc(Filesystem::getId).last("LIMIT 1");
        return getOne(queryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void moveSort(String hostId, MoveSortQuery query) {
        Filesystem move = this.getByUid(query.getMoveUid());
        AssertUtil.isNotNull(move, ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));
        AssertUtil.isTrue(move.getHostId().equals(hostId), ServiceRuntimeException.instance(ApiCodeUser.PARAMETER_ERROR));
        String parentId = StrUtil.isBlank(query.getParentId()) ? "" : query.getParentId();
        List<Filesystem> list = this.getAll(new FilesystemQuery().setHostId(hostId).setParentId(parentId));

        String oldPedigree = move.getPedigree();
        int oldGeneration = move.getGeneration();
        String oldFamilyId = move.getFamilyId();

        String lockKey = StrUtil.isBlank(query.getParentId()) ? getAddLockKey(hostId) : query.getParentId();
        Filesystem parent = StrUtil.isBlank(query.getParentId()) ? null : this.getByUid(query.getParentId());
        Lock lock = SimpleLockerUtil.getLocker(lockKey, Filesystem.class.getSimpleName());
        lock.lock();
        try {
            if(!Objects.equals(parentId ,move.getParentId())) {
                Filesystem nextLastChild = this.getLastFormNextChildrenByUid(parentId);
                DataPedigreeHelper.initCascadeField(move, parentId, parent, nextLastChild);
            }
            if (CollectionUtil.isEmpty(list)) {
                //移动目录没有文件的情况，直接更新即可;
                updateById(move);
                return;
            }
            int targetIndex = 0;
            Filesystem targetDocument = null;
            if (StrUtil.isNotBlank(query.getTargetUid())) {
                for (int i = 0; i < list.size(); i++) {
                    if (list.get(i).getUid().equals(query.getTargetUid())) {
                        targetIndex = query.isBefore() ? i - 1 : i + 1;
                        targetDocument = list.get(i);
                        break;
                    }
                }
            }
            List<Filesystem> updateList = new ArrayList<>();
            //排序值改变
            final int sortOrder = 1;
            if (targetIndex <= 0) {
                move.setSortOrder(list.get(0).getSortOrder() - sortOrder);
            } else if (targetIndex >= list.size()) {
                move.setSortOrder(list.get(list.size() - 1).getSortOrder() + sortOrder);
            } else {
                move.setSortOrder(targetDocument.getSortOrder());
                //下标之前的值-1，排列在前
                for (int i = 0; i < targetIndex; i++) {
                    Filesystem document = list.get(i);
                    document.setSortOrder(document.getSortOrder() - sortOrder);
                    updateList.add(document);

                }
                //下标之后的值+1，排列在后
                for (int i = targetIndex; i < list.size(); i++) {
                    Filesystem document = list.get(i);
                    document.setSortOrder(document.getSortOrder() + sortOrder);
                    updateList.add(document);
                }
            }

            updateList.add(move);

            //批量更新，可优化
            this.updateBatchById(updateList);
        } finally {
            lock.unlock();

            //move节点下面的孩子节点同步迁移
            if(!Objects.equals(parentId ,move.getParentId())) {
                List<Filesystem> children = this.getAllChildren(oldFamilyId ,oldPedigree);
                if(CollectionUtil.isEmpty(children)){
                    return;
                }
                int generation = move.getGeneration() - oldGeneration;
                children.forEach(child -> {
                    child.setGeneration(child.getGeneration() + generation);
                    child.setFamilyId(move.getFamilyId()).setPedigree(child.getPedigree().replace(oldPedigree, move.getPedigree()));
                });
                this.updateBatchById(children);
            }
        }

    }

    @Override
    public void removeByHostId(String hostId) {
        this.remove(new LambdaQueryWrapper<Filesystem>().eq(Filesystem::getHostId ,hostId));
    }

    @Override
    public List<Map<String, Object>> getTree(String hostId ,FilesystemQuery query) {
        query = query == null ? new FilesystemQuery() : query;
        query.setHostId(hostId);
        List<Filesystem> filesystemList = this.getAll(query);
        List<Map<String, Object>> list = TreeCascadeBuilder.buildTree(filesystemList ,"uid" ,"parentId" , "children");

        return list;
    }

    private List<Filesystem> getAllChildren(Filesystem filesystem) {
        return getAllChildren(filesystem.getFamilyId(), filesystem.getPedigree());
    }

    private List<Filesystem> getAllChildren(String familyId , String pedigree) {
        LambdaQueryWrapper<Filesystem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Filesystem::getFamilyId, familyId);
        queryWrapper.like(Filesystem::getPedigree, pedigree);
        queryWrapper.ne(Filesystem::getPedigree, pedigree);
        return this.list(queryWrapper);
    }

    /**
     * 创建基于查询条件的基础查询包装器。
     *
     * @param query 查询条件对象，包含文件夹名称和排序条件。
     * @return 返回构建好的查询包装器，用于Mybatis Plus的查询操作。
     */
    private LambdaQueryWrapper<Filesystem> createBaseWrapper(FilesystemQuery query) {
        LambdaQueryWrapper<Filesystem> queryWrapper = new LambdaQueryWrapper<>();
        if (query == null) {
            return queryWrapper;
        }
        // 根据文件夹名称进行右端匹配查询
        queryWrapper.like(StrUtil.isNotBlank(query.getName()), Filesystem::getName, query.getName());
        queryWrapper.like(StrUtil.isNotBlank(query.getHostId()), Filesystem::getHostId, query.getHostId());
        queryWrapper.eq(query.getParentId() != null, Filesystem::getParentId, query.getParentId());
        queryWrapper.eq(query.getType() != null ,Filesystem::getType ,query.getType());

        if (CollectionUtil.isNotEmpty(query.getSorts())) {
            // 如果存在自定义排序条件，则按照自定义排序进行查询
            MybatisPlusHelper.orderAndIgnoreError(Filesystem.class, queryWrapper, query.getSorts());
        } else {
            // 如果没有自定义排序，则按照排序序号进行升序查询
            queryWrapper.orderByAsc(Filesystem::getSortOrder);
        }
        return queryWrapper;
    }

    private LambdaQueryWrapper<Filesystem> createNextChildrenWrapper(String uid ,FilesystemQuery query) {
        query.setParentId(uid);
        LambdaQueryWrapper<Filesystem> queryWrapper = createBaseWrapper(query);
        //没有上级菜单就查询顶层数据
        if (query == null || StrUtil.isBlank(query.getParentId())) {
            queryWrapper.and(andWrapper -> {
                andWrapper.isNull(Filesystem::getParentId).or().eq(Filesystem::getParentId, "");
            });
        }
        return queryWrapper;
    }
}
