package cn.lihongjie.coal.netDisk.service;

import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.mapper.CommonMapper;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.common.TreeUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.netDisk.dto.*;
import cn.lihongjie.coal.netDisk.entity.NetDiskEntity;
import cn.lihongjie.coal.netDisk.mapper.NetDiskMapper;
import cn.lihongjie.coal.netDisk.repository.NetDiskRepository;
import cn.lihongjie.coal.spring.config.AliyunProperty;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Splitter;

import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.servlet.http.HttpServletResponse;

import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class NetDiskService extends BaseService<NetDiskEntity, NetDiskRepository> {
    @Autowired private CommonMapper commonMapper;
    public static final int DEFAULT_SLICE_SIZE = 10 * 1024 * 1024;
    @Autowired ObjectMapper objectMapper;
    @Autowired OSSClient ossClient;
    @Autowired AliyunProperty aliyunProperty;
    @Autowired private NetDiskRepository repository;
    @Autowired private NetDiskMapper mapper;
    @Autowired private ConversionService conversionService;

    public String idToPath(IdRequest request) {

        String id = request.getId();

        String ans = this.repository.idToPath(id);
        return StringUtils.startsWith(ans, "//") ? StringUtils.substring(ans, 1) : ans;
    }

    public String pathToId(IdRequest request) {

        String id =
                this.repository.pathToId(request.getId(), Ctx.currentUser().getOrganizationId());

        if (StringUtils.isEmpty(id)) {

            if (StringUtils.equalsIgnoreCase(request.getId(), "/")) {

                return ensureRoot().getId();
            }

            throw new BizException("路径不存在");
        }

        return id;
    }

    private NetDiskEntity ensureRoot() {

        NetDiskEntity entity = new NetDiskEntity();
        entity.setEntryType("0");
        entity.setName("/");
        entity.setOrganizationId(Ctx.currentUser().getOrganizationId());
        save(entity);

        return entity;
    }

    private NetDiskEntity findRoot() {

        try {

            return findRootDir().get(0);
        } catch (ArrayIndexOutOfBoundsException e) {

            IdRequest request = new IdRequest("/");
            handlerRootDir(request);
            return get(request.getId());
        }
    }

    private static void checkEntry(CreateFileDto request, List<NetDiskEntity> allSlice) {
        if (allSlice.isEmpty() && request.getSliceIndex() != 0) {
            throw new BizException("第一个分片的index必须是0");
        }

        if (request.getSliceIndex() != allSlice.size()) {
            throw new BizException("分片index必须是连续的");
        }
    }

    private static NetDiskEntity findExistEntry(
            CreateFileDto request, List<NetDiskEntity> allSlice, NetDiskEntity existEntry) {
        for (NetDiskEntity entity : allSlice) {

            if (entity.getSliceIndex().equals(request.getSliceIndex())) {

                if (StringUtils.equals(entity.getSliceSha256(), request.getSliceSha256())) {
                    log.info("分片 {} 已经存在, 不用上传", request.getName() + "-" + request.getSliceIndex());
                    existEntry = entity;
                    break;
                } else {
                    throw new BizException("分片已经存在, 但是sha256不一致, 请检查");
                }
            }
        }
        return existEntry;
    }

    private static String getOssKey(String sha256) {
        return "netDisk"
                + "/"
                + sha256.substring(0, 2)
                + "/"
                + sha256.substring(2, 4)
                + "/"
                + sha256.substring(4);
    }

    public List<NetDiskDto> ls(IdRequest request) {

        handlerRootDir(request);

        transPathToId(request::getId, request::setId);

        if (StringUtils.isEmpty(request.getId())) {
            return this.mapper.toDto(
                    repository.findALlByParentNullAndOrganizationId(
                            Ctx.currentUser().getOrganizationId()));
        }

        NetDiskEntity entity = get(request.getId());

        ArrayList<NetDiskEntity> ans = new ArrayList<>();

        if (entity.getChildren() != null) {

            ans.addAll(entity.getChildren());
        }

        return this.mapper.toDto(ans);
    }

    @Autowired private EntityManager entityManager;

    public List<NetDiskDto> batchCreateDir(BatchCreateDirDto request) {

        List<String> paths = request.getNames();
        if (StringUtils.isEmpty(request.getParent())) {

            paths.forEach(
                    x -> {
                        if (!x.startsWith("/")) {
                            throw new BizException("根目录下的文件夹必须以 / 开头");
                        }
                    });

            batchCreateDir(
                    findRoot(),
                    paths.stream().map(s -> s.substring(1)).collect(Collectors.toList()));

        } else {

            NetDiskEntity parent = get(request.getParent());

            paths.forEach(
                    x -> {
                        if (x.startsWith("/")) {
                            throw new BizException("子目录下的文件夹不能以 / 开头");
                        }
                    });

            batchCreateDir(parent, paths);
        }

        return null;
    }

    private void batchCreateDir(NetDiskEntity parent, List<String> paths) {

        paths.forEach(
                x ->
                        batchCreateDir0(
                                parent,
                                Splitter.on("/").trimResults().omitEmptyStrings().splitToList(x)));
    }

    private void batchCreateDir0(NetDiskEntity parent, List<String> path) {

        if (path.isEmpty()) {
            return;
        }

        String first = path.get(0);

        if (parent.getChildren() == null
                || parent.getChildren().stream().noneMatch(x -> x.getName().equals(first))) {

            NetDiskEntity entity = new NetDiskEntity();
            entity.setName(first);
            entity.setEntryType("0");
            entity.setOrganizationId(Ctx.currentUser().getOrganizationId());
            entity.setParent(parent);
            save(entity);
            if (parent.getChildren() == null) {
                parent.setChildren(new ArrayList<>());
            }
            parent.getChildren().add(entity);

            batchCreateDir0(entity, path.subList(1, path.size()));
        } else {

            batchCreateDir0(
                    parent.getChildren().stream()
                            .filter(x -> x.getName().equals(first))
                            .findFirst()
                            .get(),
                    path.subList(1, path.size()));
        }
    }

    @NotNull
    private List<NetDiskEntity> findRootDir() {
        return this.repository.findAll(
                new Specification<NetDiskEntity>() {
                    @Override
                    public Predicate toPredicate(
                            Root<NetDiskEntity> root,
                            CriteriaQuery<?> query,
                            CriteriaBuilder criteriaBuilder) {

                        return criteriaBuilder.and(
                                criteriaBuilder.isNull(root.get("parent")),
                                criteriaBuilder.equal(
                                        root.get("organizationId"),
                                        Ctx.currentUser().getOrganizationId()),
                                criteriaBuilder.equal(root.get("entryType"), "0"),
                                criteriaBuilder.equal(root.get("name"), "/"));
                    }
                });
    }

    public NetDiskDto createDir(CreateDirDto request) {

        validateDirName(request.getName());

        checkParentWithEntryName(request.getParent(), request.getName());

        NetDiskEntity entity = this.mapper.toEntity(request);
        entity.setEntryType("0");
        entity.setSize(0L);
        save(entity);
        return this.mapper.toDto(entity);
    }

    public void transPathToId(Supplier<String> path, Consumer<String> idConsumer) {

        String p = path.get();

        if (StringUtils.isNotBlank(p) && p.startsWith("/")) {

            idConsumer.accept(pathToId(new IdRequest(p)));

        } else {
            idConsumer.accept(p);
        }
    }

    private void checkParentWithEntryName(String parent, String name) {

        if (StringUtils.isEmpty(parent)) {

            long count =
                    repository.countByParentNullAndOrganizationIdAndName(
                            Ctx.currentUser().getOrganizationId(), name);

            if (count > 0) {
                throw new BizException("根目录已经存在同名文件/文件夹");
            }
        } else {

            NetDiskEntity disk = get(parent);

            if (disk.getChildren().stream().anyMatch(x -> x.getName().equals(name))) {
                throw new BizException("目录已经存在同名文件/文件夹");
            }
        }
    }

    private String createNewFileName(String parent, String name) {

        NetDiskEntity disk = get(parent);

        if (disk.getChildren().stream().anyMatch(x -> x.getName().equals(name))) {

            Pattern pattern = Pattern.compile(Pattern.quote(name) + "\\((\\d+)\\)");

            int index =
                    disk.getChildren().stream()
                            .map(x -> x.getName())
                            .mapToInt(
                                    x -> {
                                        Matcher matcher = pattern.matcher(x);

                                        if (matcher.find()) {
                                            return commonMapper.toInt(matcher.group(1));
                                        } else {
                                            return 0;
                                        }
                                    })
                            .max()
                            .orElse(1);

            return name + "(" + (index + 1) + ")";
        }

        return name;
    }

    private void validateDirName(String name) {

        if (StringUtils.isEmpty(name) || StringUtils.isBlank(name)) {
            throw new BizException("文件夹名字不能为空");
        }

        if (name.length() > 255) {
            throw new BizException("文件夹名字不能超过255个字符");
        }
    }


    @SneakyThrows
    public NetDiskDto createFile(CreateFileDto request) {

        resolveNameWithPath(request);

        validateFileName(request.getName());
        request.setName(createNewFileName(request.getParent(), request.getName()));

        if (StringUtils.isNotBlank(request.getOssKey())) {

            if (!repository.existsByOssKey(request.getOssKey())) {
                throw new BizException("OSSKey 不存在");
            }
        } else {

            try {

                String ossKey = getOssKey(request.getSha256());

                PutObjectResult objectResult =
                        ossClient.putObject(
                                aliyunProperty.getOSS().getBucketName(),
                                ossKey,
                                request.getMultipartFile().getInputStream());
                request.setOssKey(ossKey);
                log.info("上传文件 {} 到 OSS {}", request.getName(), objectResult.getResponse());

            } catch (Exception e) {
                log.warn("上传文件失败", e);
                throw new BizException("上传文件失败 " + e.getMessage());
            }
        }

        NetDiskEntity entity = this.mapper.toEntity(request);
        entity.setEntryType("1");
        entity.setSize(request.getSize());
        save(entity);

        entityManager.flush();
        
        if (entity.getParent() != null) {

            updateDirSize(entity.getParent().getId());
        }

        return this.mapper.toDto(entity);
    }

    private void resolveNameWithPath(CreateFileDto request) {

        if (request.getName().contains("/")) {
            String oldId = request.getParent();
            String[] split = request.getName().split("/");
            request.setName(split[split.length - 1]);

            NetDiskEntity current = get(request.getParent());

            for (int i = 0; i < split.length - 1; i++) {

                String name = split[i];

                if (StringUtils.isEmpty(name)) {
                    continue;
                }

                if (current.getChildren() == null
                        || current.getChildren().stream()
                                .noneMatch(x -> x.getName().equals(name))) {

                    throw new BizException("路径不存在: " + request.getName());
                } else {
                    current =
                            current.getChildren().stream()
                                    .filter(x -> x.getName().equals(name))
                                    .findFirst()
                                    .get();
                }
            }

            request.setParent(current.getId());

            log.info("解析路径 {} 为 id {}, oldId {}", request.getName(), request.getParent(), oldId);
        }
    }

    @SneakyThrows
    public NetDiskDto createFileSlice(CreateFileDto request) {

        validateFileName(request.getName());
        checkParentWithEntryName(request.getParent(), request.getName());
        NetDiskEntity existEntry = null;
        String ossKey = getOssKey(request.getSha256());

        @Cleanup InputStream inputStream = request.getMultipartFile().getInputStream();

        try {

            List<NetDiskEntity> allSlice = findSlice(request.getSha256());

            // 如果分片已经存在, 就不用上传了

            existEntry = findExistEntry(request, allSlice, existEntry);

            checkEntry(request, allSlice);

            if (existEntry == null) {

                UploadPartResult uploadPartResult =
                        doUploadPart(request, ossKey, allSlice, inputStream);

                existEntry = saveEntry(request, ossKey, allSlice, uploadPartResult);
                log.info("上传文件 {} 到 OSS {}", request.getName(), uploadPartResult.getResponse());
            }

        } catch (Exception e) {
            log.warn("上传文件失败", e);
            throw new BizException("上传文件失败 " + e.getMessage());
        }

        List<NetDiskEntity> slice = findSlice(request.getSha256());

        long totalSize = slice.stream().mapToLong(NetDiskEntity::getSliceSize).sum();

        if (totalSize == request.getSize()) {
            log.info("文件 {} 已经上传完成, 开始合并", request.getName());

            CompleteMultipartUploadResult completeMultipartUploadResult =
                    doCompleteSliceUpload(slice, ossKey);

            log.info("合并完成 {}", completeMultipartUploadResult.getResponse());

            // 生成新的文件信息

            NetDiskEntity netDiskEntity = buildNewEntry(slice, ossKey);

            // 删除分片信息

            this.repository.deleteAll(slice);

            if (netDiskEntity.getParent() != null) {

                updateDirSize(netDiskEntity.getParent().getId());
            }

        } else {
            log.info(
                    "文件 {} 还没有上传完成, 已经上传 {} 个分片, 分片总大小 {} , 文件总大小 {} ",
                    request.getName(),
                    slice.size(),
                    totalSize,
                    request.getSize());
        }

        return this.mapper.toDto(existEntry);
    }

    private NetDiskEntity buildNewEntry(List<NetDiskEntity> slice, String ossKey) {
        NetDiskEntity completeFile = mapper.copy(slice.get(0));

        completeFile.setEntryType("1");
        completeFile.setOssKey(ossKey);
        completeFile.setSliceSize(null);
        completeFile.setSliceIndex(null);
        completeFile.setSliceSha256(null);

        this.repository.save(completeFile);
        return completeFile;
    }

    private CompleteMultipartUploadResult doCompleteSliceUpload(
            List<NetDiskEntity> slice, String ossKey) {
        List<PartETag> partETags =
                slice.stream()
                        .map(
                                x -> {
                                    try {
                                        return objectMapper.readValue(
                                                x.getRemarks(), PartETag.class);
                                    } catch (JsonProcessingException e) {
                                        throw new RuntimeException(e);
                                    }
                                })
                        .collect(Collectors.toList());
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(
                        aliyunProperty.getOSS().getBucketName(),
                        ossKey,
                        slice.get(0).getSliceTaskId(),
                        partETags);
        CompleteMultipartUploadResult completeMultipartUploadResult =
                ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        return completeMultipartUploadResult;
    }

    private NetDiskEntity saveEntry(
            CreateFileDto request,
            String ossKey,
            List<NetDiskEntity> allSlice,
            UploadPartResult uploadPartResult)
            throws JsonProcessingException {
        NetDiskEntity existEntry;
        existEntry = this.mapper.toEntity(request);
        existEntry.setEntryType("2");
        existEntry.setSliceTaskId(getUploadId(request, ossKey, allSlice));
        existEntry.setRemarks(objectMapper.writeValueAsString(uploadPartResult.getPartETag()));
        save(existEntry);
        return existEntry;
    }

    private UploadPartResult doUploadPart(
            CreateFileDto request,
            String ossKey,
            List<NetDiskEntity> allSlice,
            InputStream inputStream) {
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(aliyunProperty.getOSS().getBucketName());
        uploadPartRequest.setKey(ossKey);
        uploadPartRequest.setUploadId(getUploadId(request, ossKey, allSlice));
        uploadPartRequest.setPartNumber(Math.toIntExact(request.getSliceIndex()) + 1);
        uploadPartRequest.setInputStream(inputStream);
        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
        return uploadPartResult;
    }

    private String getUploadId(CreateFileDto request, String ossKey, List<NetDiskEntity> allSlice) {
        String uploadId;
        if (request.getSliceIndex() == 0) {

            uploadId = initPartTask(ossKey);

            log.info("初始化分片上传 {}", uploadId);
        } else {

            uploadId = allSlice.get(0).getSliceTaskId();
        }
        return uploadId;
    }

    private String initPartTask(String ossKey) {
        String uploadId;
        InitiateMultipartUploadRequest multipartUploadRequest =
                new InitiateMultipartUploadRequest(aliyunProperty.getOSS().getBucketName(), ossKey);

        InitiateMultipartUploadResult multipartUpload =
                ossClient.initiateMultipartUpload(multipartUploadRequest);
        uploadId = multipartUpload.getUploadId();
        return uploadId;
    }

    private void validateFileName(String name) {

        if (StringUtils.isEmpty(name) || StringUtils.isBlank(name)) {
            throw new BizException("文件名字不能为空");
        }

        if (name.length() > 255) {
            throw new BizException("文件名字不能超过255个字符");
        }
    }

    public NetDiskDto rename(RenameDto request) {

        NetDiskEntity netDiskEntity = get(request.getId());

        if (netDiskEntity.getParent().getChildren().stream()
                .anyMatch(x -> x.getName().equals(request.getName()))) {
            throw new BizException("目录已经存在同名文件/文件夹");
        }

        netDiskEntity.setName(request.getName());

        save(netDiskEntity);

        return this.mapper.toDto(netDiskEntity);
    }

    public void remove(IdRequest request) {

        this.repository.findAllById(request.getIds()).forEach(this::remove);
    }

    private void remove(NetDiskEntity netDiskEntity) {

        this.repository.delete(netDiskEntity);

        if (netDiskEntity.getParent() != null) {

            updateDirSize(netDiskEntity.getParent().getId());
        }
    }

    public NetDiskDto move(MoveDto request) {
        NetDiskEntity target = get(request.getTargetId());
        if (!target.getEntryType().equals("0")) {
            throw new BizException("只能移动到目录");
        }

        var srcParentIds = new HashSet<String>();
        for (String srcId : request.getSrcIds()) {

            NetDiskEntity src = get(srcId);

            TreeUtils.allChildren(src, NetDiskEntity::getChildren)
                    .forEach(
                            x -> {
                                if (x.getId().equals(target.getId())) {
                                    throw new BizException("不能移动到自己的子目录");
                                }
                            });

            if (target.getChildren() != null
                    && target.getChildren().stream()
                                    .filter(x -> x.getName().equals(src.getName()))
                                    .count()
                            > 0) {
                throw new BizException("目标目录已经存在同名文件/文件夹");
            }

            src.setParent(target);

            this.save(src);

            if (src.getParent() != null) {

                srcParentIds.add(src.getParent().getId());
            }
        }

        if (CollectionUtils.isNotEmpty(srcParentIds)) {

            srcParentIds.forEach(this::updateDirSize);
        }
        if (target.getParent() != null) {

            updateDirSize(target.getParent().getId());
        }

        return null;
    }

    private void handlerRootDir(IdRequest request) {

        if (StringUtils.equals(request.getId(), "/")) {

            List<NetDiskEntity> rootDir = findRootDir();

            if (rootDir.isEmpty()) {
                NetDiskEntity entity = new NetDiskEntity();
                entity.setEntryType("0");
                entity.setName("/");
                entity.setOrganizationId(Ctx.currentUser().getOrganizationId());
                save(entity);

                request.setId(entity.getId());
            } else {
                request.setId(rootDir.get(0).getId());
            }
        }
    }

    public String downloadFile(String id, boolean attachment) {

        String bucketName = aliyunProperty.getOSS().getBucketName();
        NetDiskEntity disk = get(id);

        if (!disk.getEntryType().equals("1")) {
            throw new BizException("只能下载文件");
        }

        String ossKey = disk.getOssKey();
        Date addedMinutes =
                DateUtils.addMinutes(
                        new Date(),
                        Math.toIntExact(
                                ObjectUtils.defaultIfNull(
                                        aliyunProperty.getOSS().getPreSignedUrlExpire(), 30L)));
        GeneratePresignedUrlRequest request =
                new GeneratePresignedUrlRequest(bucketName, ossKey, HttpMethod.GET);
        request.setExpiration(addedMinutes);

        ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
        responseHeaders.setContentType(disk.getMimeType());
        if (attachment) {
            responseHeaders.setContentDisposition(
                    "attachment;filename="
                            + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8));

        } else {
            responseHeaders.setContentDisposition(
                    "inline;filename=" + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8));
        }
        request.setResponseHeaders(responseHeaders);

        URL url = ossClient.generatePresignedUrl(request);

        return url.toString();
    }

    public NetDiskDto copy(CopyDto request) {

        NetDiskEntity target = get(request.getTargetId());

        if (!target.getEntryType().equals("0")) {
            throw new BizException("只能复制到目录");
        }

        for (String srcId : request.getSrcIds()) {

            NetDiskEntity srcEntity = get(srcId);

            NetDiskEntity src = this.mapper.copyChildren(srcEntity);

            if (target.getChildren().stream().filter(x -> x.getName().equals(src.getName())).count()
                    > 0) {
                throw new BizException("目标目录已经存在同名文件/文件夹");
            }

            TreeUtils.dfs(
                    src,
                    NetDiskEntity::getChildren,
                    x -> {
                        if (x.getRoot() == x.getCurrent() && x.getParent() == null) {

                            x.getCurrent().setParent(target);
                            this.save(x.getCurrent());
                        } else {

                            x.getCurrent().setParent(x.getParent());
                            this.save(x.getCurrent());
                        }
                    },
                    null);
        }

        if (target.getParent() != null) {

            updateDirSize(target.getParent().getId());
        }
        return null;
    }

    @SneakyThrows
    public void downloadBatch(IdRequest request, HttpServletResponse response) {

        // 设置响应类型为ZIP
        response.setContentType("application/zip");

        // 设置响应头，指定ZIP文件名
        String zipName =
                URLEncoder.encode("批量下载" + LocalDate.now() + ".zip", StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition", "attachment; filename=" + zipName);
        StopWatch stopWatch = new StopWatch("批量下载 " + StringUtils.join(request.getIds(), ","));

        @Cleanup("close")
        ZipArchiveOutputStream zipArchiveOutputStream =
                new ZipArchiveOutputStream(response.getOutputStream());

        for (String id : request.getIds()) {

            NetDiskEntity entity = get(id);

            zipEntity(entity, zipArchiveOutputStream, entity, stopWatch);

            log.info(stopWatch.prettyPrint());
        }
    }

    private String getFullName(NetDiskEntity root, NetDiskEntity entry) {

        if (root == entry) {
            return entry.getName() + "/";
        }

        LinkedList<String> parts = new LinkedList<>();
        boolean isDir = entry.getEntryType().equals("0");

        while (entry != root) {
            parts.addFirst(entry.getName());
            entry = entry.getParent();
        }
        parts.addFirst(root.getName());

        return StringUtils.join(parts, "/") + (isDir ? "/" : "");
    }

    public NetDiskDto create(CreateNetDiskDto request) {
        NetDiskEntity entity = mapper.toEntity(request);

        this.repository.save(entity);
        return getById(entity.getId());
    }

    public NetDiskDto update(UpdateNetDiskDto request) {
        NetDiskEntity entity = this.repository.get(request.getId());
        this.mapper.updateEntity(entity, request);

        this.repository.save(entity);

        return getById(entity.getId());
    }

    public void delete(IdRequest request) {
        this.repository.deleteAllById(request.getIds());
    }

    public NetDiskDto getById(String id) {
        NetDiskEntity entity = repository.get(id);

        return mapper.toDto(entity);
    }

    public Page<NetDiskDto> list(CommonQuery query) {
        Page<NetDiskEntity> page =
                repository.findAll(
                        query.specification(conversionService),
                        PageRequest.of(
                                query.getPageNo(),
                                query.getPageSize(),
                                Sort.by(query.getOrders())));

        return page.map(this.mapper::toDto);
    }

    public PreCreateFileDto preCreateFile(CreateFileDto request) {

        PreCreateFileDto dto = new PreCreateFileDto();
        validateFileName(request.getName());

        //        checkParentWithEntryName(request.getParent(), request.getName());

        List<NetDiskEntity> sha256 = repository.findAllBySha256(request.getSha256());

        String ossKey = sha256.stream().findAny().map(NetDiskEntity::getOssKey).orElse(null);

        dto.setOssKey(ossKey);

        dto.setSliceSize((long) DEFAULT_SLICE_SIZE);

        if (request.getSize() > DEFAULT_SLICE_SIZE) {

            if (StringUtils.isEmpty(ossKey)) {

                // 查找分片记录
                List<NetDiskEntity> slice = findSlice(request.getSha256());

                dto.setSlices(this.mapper.toDto(slice));
            }
        }

        return dto;
    }

    private List<NetDiskEntity> findSlice(String sha256) {
        return repository.findAll(
                (root, query, criteriaBuilder) -> {
                    return criteriaBuilder.and(
                            criteriaBuilder.equal(root.get("sha256"), sha256),
                            criteriaBuilder.equal(root.get("entryType"), "2"));
                },
                Sort.by(Sort.Direction.ASC, "sliceIndex"));
    }

    public void updateDirSize(String id) {
        NetDiskEntity entity = get(id);
        entityManager.refresh(entity);

        entity.setSize(
                entity.getChildren() == null
                        ? 0
                        : entity.getChildren().stream().mapToLong(NetDiskEntity::getSize).sum());
        save(entity);

        while (entity.getParent() != null) {
            entity = entity.getParent();
            entity.setSize(
                    entity.getChildren() == null
                            ? 0
                            : entity.getChildren().stream()
                                    .mapToLong(NetDiskEntity::getSize)
                                    .sum());
            save(entity);
        }
    }

    @SneakyThrows
    public void downloadFileLocal(String id, Boolean attachment, HttpServletResponse response) {

        NetDiskEntity disk = get(id);

        if (!disk.getEntryType().equals("1")) {
            throw new BizException("只能下载文件");
        }

        String ossKey = disk.getOssKey();

        OSSObject ossObject = ossClient.getObject(aliyunProperty.getOSS().getBucketName(), ossKey);

        response.setContentType(disk.getMimeType());

        if (attachment) {
            response.setHeader(
                    "Content-Disposition",
                    "attachment;filename="
                            + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8));

        } else {

            response.setHeader(
                    "Content-Disposition",
                    "inline;filename=" + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8));
        }

        ossObject.getObjectContent().transferTo(response.getOutputStream());
    }

    public NetDiskTreeDto tree(IdRequest request) {

        NetDiskEntity entity = get(request.getId());

        return this.mapper.toTreeDto(entity);
    }

    @SneakyThrows
    private void zipEntity(
            NetDiskEntity dir,
            ZipArchiveOutputStream zipArchiveOutputStream,
            NetDiskEntity root,
            StopWatch stopWatch) {

        String fullName = getFullName(root, dir);
        if (dir.getEntryType().equals("0")) {

            stopWatch.start("创建目录 " + dir.getName() + " " + fullName + " " + dir.getId());
            zipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(fullName));

            zipArchiveOutputStream.closeArchiveEntry();

            stopWatch.stop();

            if (dir.getChildren() != null) {

                for (NetDiskEntity child : dir.getChildren()) {

                    zipEntity(child, zipArchiveOutputStream, root, stopWatch);
                }
            }

        } else {

            zipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(fullName));

            stopWatch.start("下载文件 " + dir.getName() + " " + dir.getId());
            OSSObject ossObject =
                    ossClient.getObject(aliyunProperty.getOSS().getBucketName(), dir.getOssKey());

            IOUtils.copy(ossObject.getObjectContent(), zipArchiveOutputStream);

            stopWatch.stop();
            zipArchiveOutputStream.closeArchiveEntry();
        }
    }

    public NetDiskTreeDto dirTree(IdRequest request) {

        StopWatch stopWatch = new StopWatch("查询目录树");

        stopWatch.start("查询所有子目录");
        List<String> ids = this.repository.findAllSubDir(request.getId());

        stopWatch.stop();

        stopWatch.start("查询所有子目录的实体");

        List<NetDiskEntity> subDirs = this.repository.findAllById(ids);

        stopWatch.stop();

        NetDiskEntity rootDir =
                subDirs.stream()
                        .filter(x -> StringUtils.equals(x.getId(), request.getId()))
                        .findFirst()
                        .get();

        stopWatch.start("构建目录树");
        Map<String, List<NetDiskEntity>> parentIdMap =
                subDirs.stream()
                        .collect(
                                Collectors.groupingBy(
                                        e ->
                                                e.getParent() == null
                                                        ? "root"
                                                        : e.getParent().getId()));
        stopWatch.stop();
        stopWatch.start("构建目录树2");
        for (NetDiskEntity subDir : subDirs) {

            subDir.setChildren(parentIdMap.get(subDir.getId()));
        }
        stopWatch.stop();
        stopWatch.start("toTreeDto");
        NetDiskTreeDto treeDto = this.mapper.toTreeDto(rootDir);

        stopWatch.stop();
        log.info(stopWatch.prettyPrint());

        return treeDto;
    }

    public List<NetDiskDto> idToPath2(IdRequest request) {

        String id = request.getId();

        List<String> ans = this.repository.idToPath2(id);

        List<NetDiskEntity> allById =
                this.repository.findAllById(ans).stream()
                        .sorted(Comparator.comparing(x -> ans.indexOf(x.getId())))
                        .collect(Collectors.toList());
        return this.mapper.toDto(allById);
    }

    public void rename(UpdateNetDiskDto dto) {

        NetDiskEntity entity = get(dto.getId());

        validateEntityName(entity);
        if (entity.getParent() != null) {
            if (entity.getParent().getChildren().stream()
                    .anyMatch(x -> x.getId() != dto.getId() && x.getName().equals(dto.getName()))) {
                throw new BizException("目录已经存在同名文件/文件夹");
            }
        }
        entity.setName(dto.getName());

        validateEntityName(entity);

        save(entity);
    }

    private void validateEntityName(NetDiskEntity entity) {
        if (StringUtils.equalsIgnoreCase(entity.getEntryType(), "0")) {

            validateDirName(entity.getName());

        } else if (StringUtils.equalsIgnoreCase(entity.getEntryType(), "1")) {

            validateFileName(entity.getName());
        }
    }
}
