package com.lion.lionwebsite.Service;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import com.lion.lionwebsite.Dao.normal.CustomConfigurationMapper;
import com.lion.lionwebsite.Dao.normal.ShareFileMapper;
import com.lion.lionwebsite.Dao.normal.UserMapper;
import com.lion.lionwebsite.Domain.CustomConfiguration;
import com.lion.lionwebsite.Domain.ShareFile;
import com.lion.lionwebsite.Interceptor.TaskHandlerInterceptor;
import com.lion.lionwebsite.Util.CustomUtil;
import com.lion.lionwebsite.Util.FileDownload;
import com.lion.lionwebsite.Util.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@Service
@Data
@Slf4j
public class PersonalServiceImpl{
    @Resource
    CustomConfigurationMapper configurationMapper;

    @Resource
    UserMapper userMapper;

    @Resource
    ShareFileMapper shareFileMapper;

    @Resource
    TaskHandlerInterceptor taskHandlerInterceptor;

    String StoragePath = "/storage/";

    DateTimeFormatter dateTimeFormatter = CustomUtil.dateTimeFormatter();

    ExecutorService compressThreadPool;

    public PersonalServiceImpl(CustomConfigurationMapper configurationMapper, UserMapper userMapper, ShareFileMapper shareFileMapper, TaskHandlerInterceptor taskHandlerInterceptor){
        this.configurationMapper = configurationMapper;
        this.userMapper = userMapper;
        this.shareFileMapper = shareFileMapper;
        this.taskHandlerInterceptor = taskHandlerInterceptor;
        compressThreadPool = Executors.newFixedThreadPool(1);
    }

    /**
     * 获取文件列表，同时带上分享码以及过期时间
     * @param path 路径
     * @return     文件列表
     */
    public String getFiles(String path)  {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        File root_file = new File(StoragePath + path);
        Calendar now = Calendar.getInstance();
        Calendar expireTime = Calendar.getInstance();

        //如果目标路径是文件夹
        if(root_file.isDirectory()) {
            ArrayList<Map<String, String>> fileMaps = new ArrayList<>();
            File[] originalFiles = root_file.listFiles();

            //如果文件夹里的文件不为空
            if (originalFiles != null) {
                originalFiles = Arrays.stream(originalFiles).sorted(Comparator.comparing(File::getName)).toArray(File[]::new);
                ArrayList<ShareFile> shareFiles = shareFileMapper.selectShareFilesByFilePath(root_file.getAbsolutePath());

                //遍历文件，放入文件信息以及查询对应的分享码
                for (File file : originalFiles) {
                    Map<String, String> fileMap = new LinkedHashMap<>();
                    fileMap.put("name", file.getName());
                    fileMap.put("path", file.getAbsolutePath());

                    if (file.isDirectory()) {
                        fileMap.put("type", "FOLDER");
                    } else if (file.isFile()) {
                        fileMap.put("type", "FILE");                 //处理文件大小单位
                        String fileSize = CustomUtil.fileSizeToString(file.length());
                        fileMap.put("size", fileSize);

                        Iterator<ShareFile> iterator = shareFiles.iterator();
                        while(iterator.hasNext()){
                            ShareFile shareFile = iterator.next();
                            if(shareFile.getFilePath().equals(file.getAbsolutePath())){
                                expireTime.setTime(shareFile.getExpireTime());
                                if(now.after(expireTime)){
                                    shareFileMapper.deleteShareFile(shareFile.getShareCode());
                                }
                                else {
                                    fileMap.put("shareCode", shareFile.getShareCode());
                                    fileMap.put("expireTime", dateTimeFormatter.format(shareFile.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
                                }
                                iterator.remove();
                                break;
                            }
                        }
                    }
                    fileMaps.add(fileMap);
                }
                response.success(new ObjectMapper().valueToTree(fileMaps).toString());
            }
            else
                response.failure("文件夹为空");
        }
        return response.toJSONString();
    }

    /**
     * 下载文件
     * @param request       请求对象
     * @param response      响应对象
     * @param path          目标路径
     */
    public void download(HttpServletRequest request, HttpServletResponse response, String path){
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        File file = new File(path);
        if(file.exists())
            FileDownload.export(request, response, path);

        else
            try{
                response.getWriter().print("404 NOT FOUND");
            }catch (IOException e){
                e.printStackTrace();
            }
    }

    /**
     * 上传文件
     * @param path          目标路径
     * @param fileName      文件名称
     * @param file          文件对象
     * @return              上传结果
     */
    public String uploadFile(String path, String fileName, MultipartFile file) {
        Response response = Response.generateResponse();
        log.info("上传文件：{}, 目标路径:{}", fileName, path);

        if(path == null || fileName == null || file == null){
            response.failure("参数不完整");
            return response.toJSONString();
        }

        File directory = new File(StoragePath + path);
        if(directory.isDirectory()){
            File targetFile = new File(StoragePath + path, fileName);

            if(targetFile.exists())
                response.failure("目标文件已存在");
            else
                try {
                    file.transferTo(Path.of(StoragePath + path, fileName));
                    response.success("上传成功");
                } catch (IOException e) {
                    response.failure("上传失败");
                    e.printStackTrace();
                }
        }
        else
            response.failure("该路径不存在或者不是文件夹");

        return response.toJSONString();
    }

    /**
     * 创建分享码
     * @param path              目标路径
     * @param expireHour        过期时间
     * @return                  如果成功则是分享码以及过期时间，失败则是失败原因
     */
    public String shareFile(String path, Integer expireHour) {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        Map<String, String> jsonObject = new HashMap<>();
        File file = new File(path);
        if(file.isFile()){                    //如果是文件,则生成分享码，调用此接口时不需要考虑分享已分享文件以及分享已过期文件
            String ShareCode;
            ShareCode = RandomUtil.randomString(8);
            Calendar expireTime = Calendar.getInstance();

            expireTime.add(Calendar.HOUR, expireHour);
            shareFileMapper.insertShareFile(ShareCode, path, expireTime.getTime());
            jsonObject.put("shareCode", ShareCode);
            jsonObject.put("expireTime", dateTimeFormatter.format(expireTime.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
            response.success(new ObjectMapper().valueToTree(jsonObject).toString());
        }
        else
            response.failure("此路径为文件夹或不存在");

        return response.toJSONString();
    }

    /**
     * 延长分享时间
     * @param path              目标文件路径
     * @param extendHour        延长小时数
     * @return                  延长结果
     */
    public String extendShareTime(String path, Integer extendHour) {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        Map<String, String> data = new LinkedHashMap<>();
        ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(path);

        if(shareFile != null){
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(shareFile.getExpireTime());

            calendar.add(Calendar.HOUR, extendHour);
            shareFile.setExpireTime(calendar.getTime());
            shareFileMapper.updateShareFile(shareFile);
            data.put("expireTime", dateTimeFormatter.format(shareFile.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()));
            data.put("path", shareFile.getFilePath());

            response.success(new ObjectMapper().valueToTree(data).toString());
        }
        else{
            response.failure("该文件未被分享");
        }

        return response.toJSONString();
    }

    /**
     * 取消分享
     * @param path 目标路径
     * @return 取消结果
     */
    public String cancelShare(String path) {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        ShareFile shareFile = shareFileMapper.selectShareFileByFilePath(path);

        if(shareFile != null){
            shareFileMapper.deleteShareFile(shareFile.getShareCode());
            response.success("取消分享成功");
        }
        else{
            response.failure("该文件未被分享");
        }

        return response.toJSONString();
    }

    /**
     * 获取订阅文件上次更新时间
     * @return 订阅文件上次更新时间
     */
    public String lastUpdate() {
        Response response = Response.generateResponse();
        CustomConfiguration configuration = configurationMapper.selectConfiguration(CustomConfiguration.LAST_UPDATE_SUB_TIME);
        response.success(configuration.getValue());
        return response.toJSONString();
    }

    /**
     * 获取家里的ip
     * @return 家里的ip
     */
    public String getIp(){
        Response response = Response.generateResponse();
        Map<String, String> jsonObject = new HashMap<>();
        String ip = configurationMapper.selectConfiguration(CustomConfiguration.CURRENT_IP_ADDRESS).getValue();
        String updateTime = configurationMapper.selectConfiguration(CustomConfiguration.LAST_UPDATE_IP_ADDRESS_TIME).getValue();

        jsonObject.put("ip", ip);
        jsonObject.put("lastUpdateTime", updateTime);
        response.success(new ObjectMapper().valueToTree(jsonObject).toString().replace("\"", " ").replace("\\", " "));

        return response.toJSONString();
    }

    /**
     * 打包文件
     * @param path 目标路径
     * @return  响应提交成功，因为该方法为异步执行，未完成时后辍为undone
     */
    public String compress(String path) {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        File file = new File(path);
        String finalPath = path;

        if(!file.isDirectory() || !file.exists()){
            response.failure("选中的路径不是文件夹");
            return response.toJSONString();
        }

        compressThreadPool.submit(() -> {
            try(OutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get(finalPath + ".tar***undone")));
                TarArchiveOutputStream aos = new TarArchiveOutputStream(bos)) {
                aos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);   //解除文件名长度限制
                Path dirPath = Paths.get(finalPath);
                Files.walkFileTree(dirPath, new SimpleFileVisitor<>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        ArchiveEntry entry = new TarArchiveEntry(dir.toFile(), dirPath.relativize(dir).toString());
                        aos.putArchiveEntry(entry);
                        aos.closeArchiveEntry();
                        return super.preVisitDirectory(dir, attrs);
                    }

                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        ArchiveEntry entry = new TarArchiveEntry(file.toFile(), dirPath.relativize(file).toString());
                        aos.putArchiveEntry(entry);
                        IOUtils.copy(Files.newInputStream(file.toFile().toPath()), aos);
                        aos.closeArchiveEntry();
                        return super.visitFile(file, attrs);
                    }
                });
                File targetFile = new File(finalPath + ".tar***undone");
                log.info("打包成功，重命名:" + targetFile.renameTo(new File(finalPath + ".tar")));
            }catch (IOException e){
                e.printStackTrace();
                log.info("打包失败，删除文件结果:" + new File(finalPath + ".tar***undone").delete());
            }
        });
        response.success("加入队列成功");
        return response.toJSONString();
    }

    /**
     * 删除文件
     * @param path  目标路径
     * @return      删除结果
     */
    public String deleteFile(String path) {
        Response response = Response.generateResponse();
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        File file = new File(path);

        if(FileUtil.del(file))
            response.success("删除成功");
        else
            response.failure("删除失败");

        return response.toJSONString();
    }
}
