package org.example.Utils;

import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.CompressionLevel;
import net.lingala.zip4j.model.enums.EncryptionMethod;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.function.Consumer;
import java.util.zip.CRC32;

/**
 * 与C# Lskj.Web.Core.Util.ZipUtil完全对齐的Java实现
 * 修复了ZipFile构造函数错误，确保所有方法正常工作
 */
public class ZipUtil {
    // 对应C#的ShowInfo事件（进度通知）
    public static Consumer<String> ShowInfo;

    // ------------------------------------------------------------------------
    // 一、压缩文件夹（无密码）
    // ------------------------------------------------------------------------
    public static void CompressDirectory(String dirPath, String fileName, int level, int bufferSize) throws IOException {
        File dir = new File(dirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new FileNotFoundException("待压缩目录不存在或不是有效目录：" + dirPath);
        }

        byte[] buffer = new byte[bufferSize];
        // 通过文件路径创建ZipFile（修复构造函数错误）
        try (ZipFile zipFile = new ZipFile(fileName)) {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionLevel(intToCompressionLevel(level));
            parameters.setIncludeRootFolder(false);

            CompressDirectory(dirPath, dirPath, zipFile, parameters, buffer);
        }
    }

    private static void CompressDirectory(String root, String path, ZipFile zipFile, ZipParameters parameters, byte[] buffer) throws IOException {
        root = root.trim().replaceAll("[/\\\\]+$", "") + File.separator;
        File currentDir = new File(path);

        // 压缩当前目录下的文件
        File[] files = currentDir.listFiles(File::isFile);
        if (files != null) {
            for (File file : files) {
                String relativePath = file.getCanonicalPath().replace(root, "").replace(File.separator, "/");
                parameters.setFileNameInZip(relativePath);

                try (FileInputStream fis = new FileInputStream(file)) {
                    zipFile.addStream(fis, parameters);
                }
            }
        }

        // 递归压缩子目录
        File[] subDirs = currentDir.listFiles(File::isDirectory);
        if (subDirs != null) {
            for (File subDir : subDirs) {
                String subRelativePath = subDir.getCanonicalPath().replace(root, "").replace(File.separator, "/") + "/";
                parameters.setFileNameInZip(subRelativePath);
                zipFile.addFolder(subDir, parameters);

                CompressDirectory(root, subDir.getCanonicalPath(), zipFile, parameters, buffer);
            }
        }
    }

    // ------------------------------------------------------------------------
    // 二、基础解压缩（无密码）
    // ------------------------------------------------------------------------
    public static void Extract(String zipFilePath, String extractPath, int bufferSize) throws IOException {
        extractPath = extractPath.trim().replaceAll("[/\\\\]+$", "") + File.separator;
        File targetDir = new File(extractPath);
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }

        // 通过文件路径创建ZipFile（修复构造函数错误）
        try (ZipFile zipFile = new ZipFile(zipFilePath)) {
            Path targetRoot = Paths.get(extractPath).toAbsolutePath().normalize();
            for (FileHeader fileHeader : zipFile.getFileHeaders()) {
                String entryName = fileHeader.getFileName();
                Path targetPath = resolveZipEntryPath(targetRoot, entryName);

                if (entryName.endsWith("/") || entryName.endsWith("\\")) {
                    Files.createDirectories(targetPath);
                    continue;
                }

                Path parent = targetPath.getParent();
                if (parent != null) {
                    Files.createDirectories(parent);
                }
                try (OutputStream os = Files.newOutputStream(targetPath);
                     InputStream is = zipFile.getInputStream(fileHeader)) {

                    byte[] data = new byte[bufferSize];
                    int size;
                    while ((size = is.read(data)) > 0) {
                        os.write(data, 0, size);
                    }
                }
            }
        }
    }

    // ------------------------------------------------------------------------
    // 三、带密码压缩相关方法
    // ------------------------------------------------------------------------
    private static boolean ZipFileDictory(String folderToZip, ZipFile zipFile, String parentFolderName, ZipParameters parameters) throws IOException {
        boolean res = true;
        File folder = new File(folderToZip);
        if (!folder.exists() || !folder.isDirectory()) {
            return false;
        }

        CRC32 crc = new CRC32();
        try {
            // 创建当前文件夹条目
            String dirEntryName = Paths.get(parentFolderName, folder.getName()).toString().replace(File.separator, "/") + "/";
            parameters.setFileNameInZip(dirEntryName);
            zipFile.addFolder(folder, parameters);

            // 压缩文件（带CRC校验）
            File[] files = folder.listFiles(File::isFile);
            if (files != null) {
                for (File file : files) {
                    byte[] fileBytes = Files.readAllBytes(file.toPath());
                    crc.reset();
                    crc.update(fileBytes);
                    long expectedCrc = crc.getValue();

                    String fileEntryName = Paths.get(parentFolderName, folder.getName(), file.getName()).toString().replace(File.separator, "/");
                    parameters.setFileNameInZip(fileEntryName);

                    try (FileInputStream fis = new FileInputStream(file)) {
                        zipFile.addStream(fis, parameters);
                    }

                    // 验证CRC
                    FileHeader fileHeader = zipFile.getFileHeader(fileEntryName);
                    if (fileHeader.getCrc() != expectedCrc) {
                        throw new IOException("文件CRC校验失败：" + file.getName());
                    }
                }
            }

            // 递归压缩子文件夹
            File[] subFolders = folder.listFiles(File::isDirectory);
            if (subFolders != null) {
                String newParent = Paths.get(parentFolderName, folder.getName()).toString();
                for (File subFolder : subFolders) {
                    if (!ZipFileDictory(subFolder.getCanonicalPath(), zipFile, newParent, parameters)) {
                        return false;
                    }
                }
            }
        } catch (Exception e) {
            res = false;
            throw e;
        } finally {
            System.gc();
        }
        return res;
    }

    private static boolean ZipFileDictory(String folderToZip, String zipedFile, String password) throws IOException {
        if (!new File(folderToZip).exists()) {
            return false;
        }

        // 通过路径和密码创建ZipFile（修复构造函数错误）
        char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null;
        try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionLevel(CompressionLevel.NORMAL);
            parameters.setIncludeRootFolder(false);

            if (passwordChars != null) {
                parameters.setEncryptFiles(true);
                parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
            }

            return ZipFileDictory(folderToZip, zipFile, "", parameters);
        }
    }

    private static boolean ZipManyFilesDictorys(String folderToZip, String zipedFile, String password) throws IOException {
        String[] filesOrDirs = folderToZip.split(";");
        if (filesOrDirs.length == 0) {
            return false;
        }

        Path zipPath = Paths.get(zipedFile);
        if (!Files.exists(zipPath.getParent())) {
            Files.createDirectories(zipPath.getParent());
        }

        // 通过路径和密码创建ZipFile（修复构造函数错误）
        char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null;
        try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionLevel(CompressionLevel.NORMAL);

            if (passwordChars != null) {
                parameters.setEncryptFiles(true);
                parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
            }

            for (String path : filesOrDirs) {
                path = path.trim();
                if (path.isEmpty()) continue;

                File target = new File(path);
                if (target.isDirectory()) {
                    if (!ZipFileDictory(path, zipFile, "", parameters)) return false;
                } else if (target.isFile()) {
                    if (!ZipFileWithStream(path, zipFile, parameters)) return false;
                }
            }
            return true;
        }
    }

    private static boolean ZipFileWithStream(String fileToZip, ZipFile zipFile, ZipParameters parameters) throws IOException {
        File file = new File(fileToZip);
        if (!file.exists() || file.isDirectory()) {
            throw new FileNotFoundException("指定要压缩的文件不存在：" + fileToZip);
        }

        try {
            parameters.setFileNameInZip(file.getName());
            try (FileInputStream fis = new FileInputStream(file)) {
                zipFile.addStream(fis, parameters);
            }
            return true;
        } catch (Exception e) {
            throw e;
        } finally {
            System.gc();
        }
    }

    private static boolean ZipFile(String fileToZip, String zipedFile, String password) throws IOException {
        File file = new File(fileToZip);
        if (!file.exists() || file.isDirectory()) {
            throw new FileNotFoundException("指定要压缩的文件不存在：" + fileToZip);
        }

        Path zipPath = Paths.get(zipedFile);
        if (!Files.exists(zipPath.getParent())) {
            Files.createDirectories(zipPath.getParent());
        }

        // 通过路径和密码创建ZipFile（修复构造函数错误）
        char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null;
        try (ZipFile zipFile = new ZipFile(zipedFile, passwordChars)) {
            ZipParameters parameters = new ZipParameters();
            parameters.setCompressionLevel(CompressionLevel.NORMAL);
            parameters.setFileNameInZip(file.getName());

            if (passwordChars != null) {
                parameters.setEncryptFiles(true);
                parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
            }

            try (FileInputStream fis = new FileInputStream(file)) {
                zipFile.addStream(fis, parameters);
            }
            return true;
        } catch (Exception e) {
            throw e;
        } finally {
            System.gc();
        }
    }

    // ------------------------------------------------------------------------
    // 四、对外核心压缩入口
    // ------------------------------------------------------------------------
    public static boolean GoZip(String fileToZip, String zipedFile, String password) {
        if (fileToZip == null || fileToZip.trim().isEmpty()) {
            return false;
        }
        fileToZip = fileToZip.trim();

        try {
            if (IsFilesOrFolders(fileToZip)) {
                return ZipManyFilesDictorys(fileToZip, zipedFile, password);
            }

            File target = new File(fileToZip);
            if (target.isDirectory()) {
                return ZipFileDictory(fileToZip, zipedFile, password);
            } else if (target.isFile()) {
                return ZipFile(fileToZip, zipedFile, password);
            } else {
                return false;
            }
        } catch (Exception e) {
            if (ShowInfo != null) {
                ShowInfo.accept(String.format("%s: unzip failed: %s", getCurrentTime(), e.getMessage()));
            }
            return false;
        }
    }

    private static boolean IsFilesOrFolders(String fileFolders) {
        return fileFolders.split(";").length > 1;
    }

    // ------------------------------------------------------------------------
    // 五、带密码解压
    // ------------------------------------------------------------------------
    public static boolean UnZip(String fileToUpZip, String zipedFolder, String password) {
        File zipFile = new File(fileToUpZip);
        if (!zipFile.exists()) {
            return false;
        }

        File targetDir = new File(zipedFolder);
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }

        // 通过路径和密码创建ZipFile（修复构造函数错误）
        char[] passwordChars = (password != null && !password.isEmpty()) ? password.toCharArray() : null;
        try (ZipFile zip = new ZipFile(fileToUpZip, passwordChars)) {
            Path targetRoot = Paths.get(zipedFolder).toAbsolutePath().normalize();
            for (FileHeader fileHeader : zip.getFileHeaders()) {
                String entryName = fileHeader.getFileName();
                Path targetPath = resolveZipEntryPath(targetRoot, entryName);

                if (entryName.endsWith("/") || entryName.endsWith("\\")) {
                    Files.createDirectories(targetPath);
                    if (ShowInfo != null) {
                        ShowInfo.accept(String.format("%s：创建文件夹%s", getCurrentTime(), targetPath));
                    }
                    continue;
                }

                if (ShowInfo != null) {
                    ShowInfo.accept(String.format("%s：解压文件%s", getCurrentTime(), targetPath));
                }

                Path parent = targetPath.getParent();
                if (parent != null) {
                    Files.createDirectories(parent);
                }
                try (OutputStream os = Files.newOutputStream(targetPath);
                     InputStream is = zip.getInputStream(fileHeader)) {

                    int size = 2048;
                    byte[] data = new byte[size];
                    while ((size = is.read(data)) > 0) {
                        os.write(data, 0, size);
                    }
                }
            }
            return true;
        } catch (Exception e) {
            if (ShowInfo != null) {
                ShowInfo.accept(String.format("%s: unzip failed: %s", getCurrentTime(), e.getMessage()));
            }
            return false;
        }
    }

    private static Path resolveZipEntryPath(Path targetRoot, String entryName) throws IOException {
        if (entryName == null || entryName.trim().isEmpty()) {
            throw new IOException("压缩包条目名称为空");
        }
        Path targetPath = targetRoot.resolve(entryName.replace("\\", "/")).normalize();
        if (!targetPath.startsWith(targetRoot)) {
            throw new IOException("压缩包条目路径非法：" + entryName);
        }
        return targetPath;
    }

    // ------------------------------------------------------------------------
    // 六、统计ZIP条目数（核心修复点）
    // ------------------------------------------------------------------------
    public static int GetZipFileCount(String fileToUpZip) {
        File zipFile = new File(fileToUpZip);
        if (!zipFile.exists()) {
            return 0;
        }

        // 修复：通过File对象创建ZipFile，而非FileInputStream
        try (ZipFile zip = new ZipFile(zipFile)) {
            return zip.getFileHeaders().size();
        } catch (Exception e) {
            if (ShowInfo != null) {
                ShowInfo.accept(String.format("%s: read zip failed: %s", getCurrentTime(), e.getMessage()));
            }
            return 0;
        }
    }

    // ------------------------------------------------------------------------
    // 工具方法
    // ------------------------------------------------------------------------
    private static CompressionLevel intToCompressionLevel(int intLevel) {
        return switch (intLevel) {
            case 0 -> CompressionLevel.NO_COMPRESSION;
            case 1 -> CompressionLevel.FASTEST;
            case 2, 3 -> CompressionLevel.FAST;
            case 4, 5, 6 -> CompressionLevel.NORMAL;
            case 7, 8 -> CompressionLevel.MAXIMUM;
            case 9 -> CompressionLevel.ULTRA;
            default -> CompressionLevel.NORMAL;
        };
    }

    private static String getCurrentTime() {
        return new SimpleDateFormat("HH:mm:ss").format(new Date());
    }
}
