package cn.reghao.tnb.file.app.zdisk.service;

import cn.reghao.file.api.dto.ObjectMeta;
import cn.reghao.file.api.iface.OssService;
import cn.reghao.jutil.jdk.converter.ByteConverter;
import cn.reghao.jutil.jdk.converter.ByteType;
import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.web.db.Page;
import cn.reghao.jutil.jdk.web.db.PageList;
import cn.reghao.jutil.jdk.web.result.Result;
import cn.reghao.tnb.file.app.zdisk.model.vo.DiskFileCount;
import cn.reghao.tnb.file.app.zdisk.model.vo.DiskFileDetail;
import cn.reghao.tnb.file.app.zdisk.model.vo.FileInfo;
import cn.reghao.tnb.file.app.zdisk.model.vo.NamePath;
import cn.reghao.oss.api.constant.ObjectType;
import cn.reghao.tnb.common.auth.UserContext;
import cn.reghao.tnb.common.util.StringUtil;
import cn.reghao.tnb.file.app.zdisk.db.mapper.DiskFileMapper;
import cn.reghao.tnb.file.app.zdisk.model.dto.DeleteFile;
import cn.reghao.tnb.file.app.zdisk.model.dto.UploadedFile;
import cn.reghao.tnb.file.app.zdisk.model.dto.MoveFile;
import cn.reghao.tnb.file.app.zdisk.model.po.DiskFile;
import cn.reghao.tnb.file.app.zdisk.model.query.DiskQuery;
import cn.reghao.tnb.file.app.zdisk.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;

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

/**
 * @author reghao
 * @date 2023-11-10 23:48:48
 */
@Slf4j
@Service
public class DiskFileService {
    @DubboReference(check = false, timeout = 60_000)
    private OssService ossService;
    private final DiskFileMapper diskFileMapper;
    private final ByteConverter byteConverter;

    public DiskFileService(DiskFileMapper diskFileMapper) {
        this.diskFileMapper = diskFileMapper;
        this.byteConverter = new ByteConverter();
    }

    public Result addDiskFile(UploadedFile uploadedFile) {
        String pid = uploadedFile.getPid();
        DiskFile diskFile1 = findByFileId(pid);
        if (diskFile1 == null) {
            return Result.fail("folder not exist");
        }
        String parentPath = diskFile1.getPath();
        try {
            int channelCode = uploadedFile.getChannelCode();
            String uploadId = uploadedFile.getUploadId();
            ObjectMeta objectMeta = ossService.getObjectMeta(uploadId);
            String filename = objectMeta.getFilename();
            String path = FileUtil.getCanonicalPath(String.format("%s/%s", parentPath, filename));
            DiskFile diskFile2 = findByPath(path);
            if (diskFile2 != null) {
                return Result.fail("file exist");
            }

            long size = objectMeta.getSize();
            int fileType = objectMeta.getFileType();
            String sha256sum = objectMeta.getSha256sum();
            DiskFile diskFile = new DiskFile(channelCode, uploadId, pid, path, filename, fileType, sha256sum, size);
            diskFileMapper.save(diskFile);
            return Result.success();
        } catch (Exception e) {
            log.error("{}", e.getMessage());
        }
        return Result.fail("上传文件失败");
    }

    public Result deleteDiskFile(DeleteFile deleteFile) {
        List<String> fileIds = deleteFile.getFileIds();
        List<DiskFile> list = findByFileIds(fileIds);

        // 删除文件
        Map<String, List<DiskFile>> groupMap0 = list.stream()
                .filter(diskFile -> diskFile.getFileType() != ObjectType.Dir.getCode())
                .collect(Collectors.groupingBy(DiskFile::getPid));
        if (groupMap0.size() == 1) {
            // 保证 file 具有相同的 pid
            List<String> fileIds0 = groupMap0.values().stream()
                    .flatMap(Collection::stream)
                    .map(DiskFile::getFileId)
                    .collect(Collectors.toList());
            if (!fileIds0.isEmpty()) {
                diskFileMapper.updateDeleteDiskFiles(fileIds0);
            }
        } else if (groupMap0.size() > 1) {
            return Result.fail("只能删除同一个文件夹下的文件");
        }

        // 删除文件夹
        Map<String, List<DiskFile>> groupMap = list.stream()
                .filter(diskFile -> diskFile.getFileType() == ObjectType.Dir.getCode())
                .collect(Collectors.groupingBy(DiskFile::getPid));
        if (groupMap.size() == 1) {
            // 保证 folder 具有相同的 pid
            List<String> foderPaths = groupMap.values().stream()
                    .flatMap(Collection::stream)
                    .map(DiskFile::getPath)
                    .collect(Collectors.toList());

            if (!foderPaths.isEmpty()) {
                long loginUser = UserContext.getUserId();
                foderPaths.forEach(path -> {
                    diskFileMapper.updateDeleteByPathPrefix(loginUser, path);
                });
            }
        } else if (groupMap.size() > 1) {
            return Result.fail("只能删除同一个文件夹下的子文件夹");
        }

        return Result.success();
    }

    public void restore(List<String> fileIds) {
        List<DiskFile> list = findByFileIds(fileIds);
        List<String> files = list.stream()
                .filter(diskFile -> diskFile.getFileType() != ObjectType.Dir.getCode())
                .map(DiskFile::getFileId)
                .collect(Collectors.toList());

        list.stream()
                .filter(diskFile -> diskFile.getFileType() == ObjectType.Dir.getCode())
                .map(diskFile1 -> getChildren(diskFile1.getPath()))
                .flatMap(Collection::stream)
                .forEach(diskFile -> {
                    files.add(diskFile.getFileId());
                });

        if (!files.isEmpty()) {
            //diskFile1Repository.updateRestoreFiles(files);
        }
    }

    public void erase(List<String> fileIds) {
        List<DiskFile> list = findByFileIds(fileIds);
        List<String> files = list.stream()
                .filter(diskFile -> diskFile.getFileType() != ObjectType.Dir.getCode())
                .map(DiskFile::getFileId)
                .collect(Collectors.toList());

        list.stream()
                .filter(diskFile -> diskFile.getFileType() == ObjectType.Dir.getCode())
                .map(diskFile -> findByPathPrefix(diskFile.getPath()))
                .flatMap(Collection::stream)
                .forEach(diskFile -> {
                    files.add(diskFile.getFileId());
                });

        if (!files.isEmpty()) {
            //diskFile1Repository.deleteByFileIds(files);
        }
    }

    public Result rename(String fileId, String newFilename, long owner) {
        DiskQuery diskQuery = new DiskQuery.Builder()
                .fileId(fileId)
                .build();
        Page page = new Page(1, 10);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        DiskFile current = diskFileList.get(0);
        if (current == null) {
            String msg = String.format("file %s not exist", fileId);
            return Result.fail(msg);
        }

        String parentPath = getParentPath(current.getPath());
        String newPath = String.format("%s/%s", parentPath, newFilename);

        diskQuery = new DiskQuery.Builder()
                .owner(owner)
                .path(newPath)
                .build();
        page = new Page(1, 10);
        diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        DiskFile diskFile = diskFileList.get(0);
        if (diskFile != null) {
            String msg = String.format("filename %s exist", newFilename);
            return Result.fail(msg);
        }

        current.setPath(newPath);
        current.setFilename(newFilename);
        List<DiskFile> diskFiles = new ArrayList<>();
        diskFiles.add(current);
        if (current.getFileType() == ObjectType.Dir.getCode()) {
            getChildren(current.getPath()).stream()
                    .filter(childFile -> !childFile.getFileId().equals(fileId))
                    .forEach(childFile -> {
                        String filename = childFile.getFilename();
                        NamePath namePath = getUniqueNamePath(newPath, filename);

                        childFile.setPath(namePath.getPath());
                        childFile.setFilename(namePath.getFilename());
                        diskFiles.add(childFile);
                    });
        }

        if (!diskFiles.isEmpty()) {
            //diskFile1Repository.updateFilenames(diskFileList);
        }

        return Result.success();
    }

    public Result move(MoveFile moveFile) {
        long owner = UserContext.getUserId();
        String pid = moveFile.getPid();
        List<String> fileIds = moveFile.getFileIds();
        boolean copy = false;

        DiskQuery diskQuery = new DiskQuery.Builder()
                .fileId(pid)
                .build();
        Page page = new Page(1, 1);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        DiskFile parentFile = diskFileList.get(0);
        if (parentFile == null) {
            String msg = "文件被移动/复制到的位置不存在";
            return Result.fail(msg);
        }

        if (parentFile.getFileType() != ObjectType.Dir.getCode()) {
            String msg = "文件被移动/复制到的位置不是文件夹";
            return Result.fail(msg);
        }

        Set<String> parents = new HashSet<>();
        String pid1 = parentFile.getPid();
        parents.add(pid1);
        while (!pid1.equals("0")) {
            diskQuery = new DiskQuery.Builder()
                    .fileId(pid1)
                    .build();
            page = new Page(1, 10);
            diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
            pid1 = diskFileList.get(0).getPid();
            parents.add(pid1);
        }

        for (String fileId : fileIds) {
            if (parents.contains(fileId)) {
                String msg = "文件被移动/复制到的位置不能是其自身或子文件夹";
                return Result.fail(msg);
            }
        }

        List<DiskFile> list = findByFileIds(fileIds);
        if (list.isEmpty()) {
            String msg = "被移动/复制的文件不存在";
            return Result.fail(msg);
        }

        String currentPid = list.get(0).getPid();
        if (currentPid.equals(pid)) {
            String msg = "移动/复制的目的文件夹和当前相同";
            return Result.fail(msg);
        }

        Map<String, List<DiskFile>> groupMap = list.stream().collect(Collectors.groupingBy(DiskFile::getPid));
        if (groupMap.size() != 1 || groupMap.get(currentPid) == null) {
            String msg = "被移动/复制的文件必须来自同一文件夹";
            return Result.fail(msg);
        }

        List<DiskFile> copiedFiles = list.stream()
                .filter(diskFile -> diskFile.getFileType() != ObjectType.Dir.getCode())
                .peek(diskFile -> {
                    setRootPath(diskFile, parentFile, owner);
                    if (copy) {
                        reinitDiskFile(diskFile);
                    }
                }).collect(Collectors.toList());

        List<DiskFile> list1 = new ArrayList<>();
        list.stream()
                .filter(diskFile -> diskFile.getFileType() == ObjectType.Dir.getCode())
                .forEach(dirFile -> {
                    List<DiskFile> results = getChildren(dirFile.getPath());
                    DiskFile diskFile = list2tree(dirFile.getFileId(), results);

                    moveRoot(diskFile, parentFile, copy, owner);
                    List<DiskFile> list2 = new ArrayList<>();
                    tree2List(diskFile, list2);
                    list1.addAll(list2);
                });

        copiedFiles.addAll(list1);
        if (!copiedFiles.isEmpty()) {
            if (copy) {
                 diskFileMapper.saveAll(copiedFiles);
            } else {
                copiedFiles.forEach(diskFileMapper::updateMoveDiskFile);
            }
        }

        return Result.success();
    }

    private void moveRoot(DiskFile current, DiskFile parent, boolean reset, long owner) {
        String oldParentPath1 = current.getPath();
        setRootPath(current, parent, owner);
        if (reset) {
            reinitDiskFile(current);
        }

        List<DiskFile> list = current.getChildren();
        if (!list.isEmpty()) {
            for (DiskFile child : list) {
                moveNode(child, current, oldParentPath1, reset);
            }
        }
    }

    private void moveNode(DiskFile current, DiskFile parent, String oldParentPath, boolean reset) {
        String oldParentPath1 = current.getPath();
        setNodePath(current, parent, oldParentPath, reset);
        if (reset) {
            reinitDiskFile(current);
        }

        List<DiskFile> list = current.getChildren();
        if (!list.isEmpty()) {
            for (DiskFile child : list) {
                moveNode(child, current, oldParentPath1, reset);
            }
        }
    }

    private void setRootPath(DiskFile current, DiskFile parent, long owner) {
        String pid = parent.getFileId();
        String parentPath = parent.getPath();

        String filename = current.getFilename();
        NamePath namePath = getUniqueNamePath(parentPath, filename);
        current.setPid(pid);
        current.setPath(namePath.getPath());
        current.setFilename(namePath.getFilename());
    }

    private void setNodePath(DiskFile current, DiskFile parent, String oldParentPath, boolean resetPid) {
        String pid = parent.getFileId();
        String parentPath = parent.getPath();
        String newPath = current.getPath().replace(oldParentPath, parentPath);
        current.setPath(newPath);

        if (resetPid) {
            current.setPid(pid);
        }
    }

    /**
     * 重新初始化
     *
     * @param
     * @return
     * @date 2023-11-17 13:38:20
     */
    private void reinitDiskFile(DiskFile diskFile) {
        String fileId = UUID.randomUUID().toString().replace("-", "");
        diskFile.setFileId(fileId);
        diskFile.setId(null);
        diskFile.setCreateTime(null);
        diskFile.setUpdateTime(null);
    }

    public List<DiskFile> getChildren(String path) {
        return findByPathPrefix(path);
    }

    public DiskFile list2tree(String rootId, List<DiskFile> list) {
        Map<String, DiskFile> map = list.stream()
                .collect(Collectors.toMap(DiskFile::getFileId, diskFile -> diskFile));
        list.forEach(diskFile -> {
            String pid = diskFile.getPid();
            String fileId = diskFile.getFileId();
            if (!fileId.equals(rootId)) {
                DiskFile parent = map.get(pid);
                parent.getChildren().add(diskFile);
            }
        });

        return map.get(rootId);
    }

    public void tree2List(DiskFile root, List<DiskFile> list) {
        list.add(root);
        for (DiskFile child : root.getChildren()) {
            tree2List(child, list);
        }
    }

    public NamePath getUniqueNamePath(String parentPath, String filename) {
        String path = getPath(parentPath, filename);
        DiskFile diskFile = findByPath(path);
        if (diskFile != null) {
            filename = getNewFilename(filename);
            path = getPath(parentPath, filename);
        }

        return new NamePath(filename, path);
    }

    /**
     * 当前文件夹中存在同名文件时, 自动修改文件名
     *
     * @param
     * @return
     * @date 2025-09-01 16:50:36
     */
    private String getNewFilename(String filename) {
        String suffix = StringUtil.getSuffix(filename);
        String prefix = filename.replace(suffix, "");
        // yyyymmdd_hh:mm:ss 格式字符串
        String dateTimeStr = DateTimeConverter.format(LocalDateTime.now())
                .replace(" ", "_")
                .replace("-", "")
                .replace(":", "");
        return String.format("%s-%s", prefix, dateTimeStr + suffix);
    }

    private String getPath(String parentPath, String filename) {
        String path;
        if (parentPath.equals("/")) {
            path = parentPath + filename;
        } else {
            path = String.format("%s/%s" , parentPath, filename);
        }

        return path;
    }

    private String getParentPath(String path) {
        int index = path.lastIndexOf("/");
        return path.substring(0, index);
    }
    
    public List<NamePath> getPathList(String path) {
        List<NamePath> pathList = new ArrayList<>();
        if (path.equals("/")) {
            NamePath namePath = new NamePath("/", "/");
            //pathList.add(namePath);
        } else {
            while (!path.equals("/")) {
                int index = path.lastIndexOf("/");
                String name = path.substring(index+1);
                NamePath namePath = new NamePath(name, path);
                pathList.add(namePath);

                if (index == 0) {
                    path = path.substring(0, index+1);
                } else {
                    path = path.substring(0, index);
                }
            }

            NamePath namePath = new NamePath("/", "/");
            pathList.add(namePath);
        }

        Collections.reverse(pathList);
        return pathList;
    }

    public List<DiskFile> findBySha256sum(String sha256sum) {
        long owner = UserContext.getUserId();
        DiskQuery diskQuery = new DiskQuery.Builder()
                .sha256sum(sha256sum)
                .owner(owner)
                .build();
        Page page = new Page(1, 10);
        return diskFileMapper.findDiskQueryByPage(page, diskQuery);
    }

    public DiskFile findByPath(String path) {
        long owner = UserContext.getUserId();
        DiskQuery diskQuery = new DiskQuery.Builder()
                .path(path)
                .owner(owner)
                .build();
        Page page = new Page(1, 1);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        return diskFileList.isEmpty() ? null : diskFileList.get(0);
    }

    public DiskFile findByFileId(String fileId) {
        if (fileId.equals("0")) {
            return diskFileMapper.findRoot();
        }

        long owner = UserContext.getUserId();
        DiskQuery diskQuery = new DiskQuery.Builder()
                .fileId(fileId)
                .owner(owner)
                .build();
        Page page = new Page(1, 1);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        return diskFileList.isEmpty() ? null : diskFileList.get(0);
    }

    public List<DiskFile> findByFileIds(List<String> fileIds) {
        if (fileIds.isEmpty()) {
            return Collections.emptyList();
        }

        return diskFileMapper.findByFileIds(fileIds);
    }

    public List<DiskFile> findByParentIds(List<String> parentIds) {
        if (parentIds.isEmpty()) {
            return Collections.emptyList();
        }

        return diskFileMapper.findByParentIds(parentIds);
    }

    public List<DiskFile> findBySha256sumList(List<String> sha256sumList) {
        if (sha256sumList.isEmpty()) {
            return Collections.emptyList();
        }

        return diskFileMapper.findSha256sumGroup(sha256sumList);
    }

    private List<DiskFile> findByPathPrefix(String pathPrefix) {
        long owner = UserContext.getUserId();
        DiskQuery diskQuery = new DiskQuery.Builder()
                .pathPrefix(pathPrefix)
                .owner(owner)
                .build();
        Page page = new Page(1, 1000);
        return diskFileMapper.findDiskQueryByPage(page, diskQuery);
    }

    public PageList<FileInfo> findByDiskQuery(DiskQuery diskQuery) {
        int pageNumber = diskQuery.getPageNumber();
        int pageSize = diskQuery.getPageSize();
        Page page = new Page(pageNumber, pageSize);
        int total = diskFileMapper.countByDiskQuery(diskQuery);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);

        List<FileInfo> list = diskFileList.stream().map(diskFile -> {
            String sizeStr = byteConverter.convert(ByteType.Bytes, diskFile.getSize());
            return new FileInfo(diskFile, sizeStr);
        }).collect(Collectors.toList());

        return PageList.pageList(pageNumber, pageSize, total, list);
    }

    // @Cacheable(cacheNames = "diskFiles", key = "#owner + '-' + #path")
    public DiskFileDetail getDiskFileDetail(String fileId) {
        long loginUser = UserContext.getUserId();
        DiskQuery diskQuery = new DiskQuery.Builder()
                .fileId(fileId)
                .owner(loginUser)
                .build();

        Page page = new Page(1, 10);
        List<DiskFile> diskFileList = diskFileMapper.findDiskQueryByPage(page, diskQuery);
        if (!diskFileList.isEmpty()) {
            DiskFile diskFile = diskFileList.get(0);
            long owner = diskFile.getOwner();
            if (owner != loginUser) {
                return null;
            }

            int channelCode = 111;
            String objectId = diskFile.getFileId();
            return getDiskFileDetail(channelCode, objectId);
        }

        return null;
    }

    public DiskFileDetail getDiskFileDetail(int channelCode, String objectId) {
        try {
            ObjectMeta objectMeta = ossService.getObjectMeta(objectId);
            String signedUrl = ossService.getSignedUrl(objectId, null);
            return new DiskFileDetail(objectMeta, signedUrl);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
        }

        return null;
    }

    public Map<Integer, DiskFileCount> getFileTypeCount() {
        long loginUser = UserContext.getUserId();
        List<DiskFileCount> list = diskFileMapper.findFileTypeCountByGroup(loginUser);
        return list.stream().collect(Collectors.toMap(DiskFileCount::getFileType, k -> k));
    }
}
