package cn.nianxx.thhotel.integrations.document.libreoffice.adapter;

import cn.nianxx.thhotel.platform.documentconversion.common.dto.ExcelToPdfConversionInput;
import cn.nianxx.thhotel.platform.documentconversion.common.dto.ExcelToPdfConvertedDocument;
import cn.nianxx.thhotel.platform.documentconversion.service.DocumentConversionException;
import cn.nianxx.thhotel.platform.documentconversion.service.ExcelToPdfConverter;
import cn.nianxx.thhotel.platform.documentconversion.service.impl.DocumentConversionProperties;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

/**
 * LibreOffice Excel 转 PDF 适配器。负责临时目录、独立 profile、soffice 命令和 PDF 输出校验。
 */
@Component
public class LibreOfficeExcelToPdfAdapter implements ExcelToPdfConverter {

    private final DocumentConversionProperties properties;
    private final LibreOfficeProcessRunner processRunner;

    /**
     * 注入文件转换配置和 LibreOffice 进程执行端口。
     */
    public LibreOfficeExcelToPdfAdapter(
            DocumentConversionProperties properties,
            LibreOfficeProcessRunner processRunner) {
        this.properties = properties;
        this.processRunner = processRunner;
    }

    /**
     * 将 Excel 字节写入隔离临时目录后调用 LibreOffice 生成 PDF，并在结束后清理临时文件。
     */
    @Override
    public ExcelToPdfConvertedDocument convert(ExcelToPdfConversionInput input) {
        Instant startedAt = Instant.now();
        Path workDirectory = null;
        try {
            Path tempRoot = tempRoot();
            Files.createDirectories(tempRoot);
            workDirectory = Files.createTempDirectory(tempRoot, "excel-to-pdf-");
            Path inputDirectory = Files.createDirectories(workDirectory.resolve("input"));
            Path outputDirectory = Files.createDirectories(workDirectory.resolve("output"));
            Path profileDirectory = Files.createDirectories(workDirectory.resolve("lo-profile"));
            String sourceFileName = safeFileName(input.fileName(), "source.xlsx");
            Path inputFile = inputDirectory.resolve(sourceFileName);
            Files.write(inputFile, input.content() == null ? new byte[0] : input.content());
            LibreOfficeProcessResult processResult = processRunner.run(new LibreOfficeProcessRequest(
                    command(inputFile, outputDirectory, profileDirectory),
                    inputDirectory,
                    inputFile,
                    outputDirectory,
                    profileDirectory,
                    Duration.ofSeconds(Math.max(1, properties.getTimeoutSeconds()))));
            validateProcessResult(processResult);
            Path pdfFile = outputDirectory.resolve(pdfFileName(sourceFileName));
            validatePdfFile(pdfFile);
            byte[] pdfBytes = Files.readAllBytes(pdfFile);
            return new ExcelToPdfConvertedDocument(
                    pdfFile.getFileName().toString(),
                    MediaType.APPLICATION_PDF_VALUE,
                    (long) pdfBytes.length,
                    pdfBytes,
                    Duration.between(startedAt, Instant.now()).toMillis());
        } catch (DocumentConversionException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_FAILED",
                    "Excel 转 PDF 失败。",
                    exception);
        } finally {
            cleanup(workDirectory);
        }
    }

    /**
     * 构建 LibreOffice headless 命令。
     */
    private List<String> command(Path inputFile, Path outputDirectory, Path profileDirectory) {
        String sofficePath = properties.getSofficePath() == null || properties.getSofficePath().isBlank()
                ? "soffice"
                : properties.getSofficePath();
        return List.of(
                sofficePath,
                "--headless",
                "--nologo",
                "--nofirststartwizard",
                "--norestore",
                "-env:UserInstallation=" + profileDirectory.toUri(),
                "--convert-to",
                "pdf",
                "--outdir",
                outputDirectory.toString(),
                inputFile.toString());
    }

    /**
     * 校验 LibreOffice 进程结果。
     */
    private void validateProcessResult(LibreOfficeProcessResult result) {
        if (result == null) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_FAILED",
                    "LibreOffice 未返回转换结果。");
        }
        if (result.timedOut()) {
            throw new DocumentConversionException(
                    HttpStatus.GATEWAY_TIMEOUT,
                    "DOCUMENT_CONVERSION_TIMEOUT",
                    "Excel 转 PDF 超时。");
        }
        if (result.exitCode() != 0) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_FAILED",
                    "LibreOffice 转换失败。");
        }
    }

    /**
     * 校验 PDF 文件存在且非空。
     */
    private void validatePdfFile(Path pdfFile) throws IOException {
        if (!Files.exists(pdfFile) || Files.size(pdfFile) <= 0) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_FAILED",
                    "Excel 转 PDF 未生成有效文件。");
        }
    }

    /**
     * 获取转换临时根目录。
     */
    private Path tempRoot() {
        String configured = properties.getTempDir();
        String tempDir = configured == null || configured.isBlank()
                ? System.getProperty("java.io.tmpdir") + "/th-hotel-document-conversion"
                : configured;
        return Path.of(tempDir);
    }

    /**
     * 生成 PDF 文件名。
     */
    private String pdfFileName(String sourceFileName) {
        String baseName = sourceFileName;
        int dotIndex = baseName.lastIndexOf('.');
        if (dotIndex > 0) {
            baseName = baseName.substring(0, dotIndex);
        }
        return baseName + ".pdf";
    }

    /**
     * 文件名安全清洗，避免路径穿越和日志污染。
     */
    private String safeFileName(String fileName, String fallback) {
        String candidate = fileName == null || fileName.isBlank() ? fallback : fileName;
        int slashIndex = Math.max(candidate.lastIndexOf('/'), candidate.lastIndexOf('\\'));
        if (slashIndex >= 0) {
            candidate = candidate.substring(slashIndex + 1);
        }
        String sanitized = candidate.replaceAll("[^A-Za-z0-9._-]", "_");
        if (sanitized.isBlank()) {
            sanitized = fallback;
        }
        return sanitized.length() > 160 ? sanitized.substring(sanitized.length() - 160) : sanitized;
    }

    /**
     * 清理单次转换临时目录。
     */
    private void cleanup(Path workDirectory) {
        if (workDirectory == null || !Files.exists(workDirectory)) {
            return;
        }
        try (var paths = Files.walk(workDirectory)) {
            paths.sorted(Comparator.reverseOrder()).forEach(path -> {
                try {
                    Files.deleteIfExists(path);
                } catch (IOException ignored) {
                    // 临时文件清理失败不覆盖原始转换结果；运维可通过系统临时目录清理策略兜底。
                }
            });
        } catch (IOException ignored) {
            // 临时目录遍历失败不覆盖原始转换结果。
        }
    }
}
