package com.multictrl.common.utils.kmz;

import com.multictrl.common.exception.RenException;
import com.multictrl.modules.business.dto.RouteDTO;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * kmz工具类
 *
 * @author Sdy
 * @since 1.0.0 2026/4/21
 */
public class KmzUtils {
    private static final String WPMZ_TEMPLATE_KML = "wpmz/template.kml";
    private static final String WPMZ_WAYLINES_WPML = "wpmz/waylines.wpml";

    /**
     * 生成byte[]数组
     */
    public static byte[] generateKmzBytes(RouteDTO route) throws IOException {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ZipOutputStream zos = new ZipOutputStream(byteArrayOutputStream)) {

            // 添加 wpmz/template.kml 文件
            String kmlContent = TemplateUtils.createTemplateContent(route);
            addEntry(zos, WPMZ_TEMPLATE_KML, kmlContent);

            // 添加 wpmz/waylines.wpl 文件
            String waylinesContent = WaylinesUtils.createTemplateContent(route);
            addEntry(zos, WPMZ_WAYLINES_WPML, waylinesContent);

            zos.finish();
            return byteArrayOutputStream.toByteArray();
        }
    }

    /**
     * 将指定内容写入 ZIP 中指定路径
     */
    private static void addEntry(ZipOutputStream zos, String path, String content) throws IOException {
        zos.putNextEntry(new ZipEntry(path));
        zos.write(content.getBytes(StandardCharsets.UTF_8));
        zos.closeEntry();
    }

    /**
     * 将 zip 输入流解压到目标目录，完整保留 zip 内的目录结构。
     *
     * @param zipStream zip 输入流（调用方负责关闭外层流）
     * @param destDir   目标目录
     * @throws IOException 解压失败
     */
    public static void unzipToDirectory(InputStream zipStream, Path destDir) throws IOException {
        // 确保目标目录存在（已存在时不会重复创建）
        Files.createDirectories(destDir);
        // 获取规范化的目标目录路径（用于安全校验）
        Path normalizedDest = destDir.toAbsolutePath().normalize();

        try (ZipInputStream zis = new ZipInputStream(zipStream)) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                // 拼接并规范化目标文件路径
                Path targetPath = normalizedDest.resolve(entryName).normalize();

                // 防止 Zip Slip 攻击：确保解压路径仍在目标目录内
                if (!targetPath.startsWith(normalizedDest)) {
                    throw new IOException("非法路径：" + entryName);
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(targetPath);
                } else {
                    // 创建父目录（如果不存在）
                    Path parent = targetPath.getParent();
                    if (parent != null) {
                        Files.createDirectories(parent);
                    }
                    // 使用 NIO 方式将 zip 条目内容写入文件
                    try (OutputStream out = Files.newOutputStream(targetPath)) {
                        byte[] buffer = new byte[8192];
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            out.write(buffer, 0, len);
                        }
                    }
                }
                zis.closeEntry();
            }
        }
    }

    /**
     * 解析 KMZ 文件，提取指定内容为 byte[]
     *
     * @param inputStream KMZ 文件流
     * @return 包含文件名与 byte[] 的映射
     * @throws IOException IO 异常
     */
    public static Map<String, byte[]> extractKmzAsBytes(InputStream inputStream) throws IOException {
        Map<String, byte[]> result = new HashMap<>();

        try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(inputStream)) {
            ZipArchiveEntry entry;
            while ((entry = zipStream.getNextEntry()) != null) {
                String entryName = entry.getName();

                if (WPMZ_TEMPLATE_KML.equals(entryName)) {
                    result.put("template.kml", readStreamToBytes(zipStream));
                } else if (WPMZ_WAYLINES_WPML.equals(entryName)) {
                    result.put("waylines.wpml", readStreamToBytes(zipStream));
                }
            }
        }

        return result;
    }

    /**
     * 将 InputStream 读取为 byte[]
     *
     * @param inputStream 输入流
     * @return 字节数组
     * @throws IOException IO 异常
     */
    private static byte[] readStreamToBytes(InputStream inputStream) throws IOException {
        try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
            byte[] data = new byte[1024];
            int bytesRead;

            while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, bytesRead);
            }

            return buffer.toByteArray();
        }
    }

    /**
     * 将 zip 输入流解压到目标目录，完整保留 zip 内的目录结构。
     *
     * @param zipStream zip 输入流（调用方负责关闭外层流）
     * @param destDir   目标目录
     * @throws IOException 解压失败
     */
    public static void unzipToDirectory(InputStream zipStream, File destDir) throws Exception {
        if (!destDir.exists()) {
            Files.createDirectories(destDir.toPath());
        }

        try (ZipInputStream zis = new ZipInputStream(zipStream)) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                File targetFile = new File(destDir, entryName);
                Path targetPath = targetFile.getCanonicalFile().toPath();
                Path destPath = destDir.getCanonicalFile().toPath();
                if (!targetPath.startsWith(destPath)) {
                    throw new RenException("非法路径：" + entryName);
                }

                if (entry.isDirectory()) {
                    Files.createDirectories(targetFile.toPath());
                } else {
                    Files.createDirectories(targetFile.getParentFile().toPath());
                    try (FileOutputStream fos = new FileOutputStream(targetFile)) {
                        byte[] buffer = new byte[8192];
                        int len;
                        while ((len = zis.read(buffer)) > 0) {
                            fos.write(buffer, 0, len);
                        }
                    }
                }
                zis.closeEntry();
            }
        }
    }
}
