package cn.nianxx.thhotel.platform.documentconversion.service.impl;

import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.request.ObjectStoragePutRequest;
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.result.ObjectStoragePutResult;
import cn.nianxx.thhotel.integrations.storage.aliyunoss.service.ObjectStorageService;
import cn.nianxx.thhotel.platform.documentconversion.common.dto.ExcelToPdfConversionInput;
import cn.nianxx.thhotel.platform.documentconversion.common.dto.ExcelToPdfConvertedDocument;
import cn.nianxx.thhotel.platform.documentconversion.common.enums.DocumentConversionStatus;
import cn.nianxx.thhotel.platform.documentconversion.common.result.ExcelToPdfConversionResult;
import cn.nianxx.thhotel.platform.documentconversion.service.DocumentConversionException;
import cn.nianxx.thhotel.platform.documentconversion.service.DocumentConversionService;
import cn.nianxx.thhotel.platform.documentconversion.service.ExcelToPdfConverter;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

/**
 * 平台文件转换服务实现。负责访问口令、文件校验、转换编排、OSS 输出和安全错误转换。
 */
@Service
public class DocumentConversionServiceImpl implements DocumentConversionService {

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;

    private final DocumentConversionProperties properties;
    private final ExcelToPdfConverter excelToPdfConverter;
    private final ObjectStorageService objectStorageService;
    private final Semaphore conversionSemaphore;

    /**
     * 注入转换配置、Excel 转 PDF 端口和 OSS 上传端口。
     */
    public DocumentConversionServiceImpl(
            DocumentConversionProperties properties,
            ExcelToPdfConverter excelToPdfConverter,
            ObjectStorageService objectStorageService) {
        this.properties = properties;
        this.excelToPdfConverter = excelToPdfConverter;
        this.objectStorageService = objectStorageService;
        this.conversionSemaphore = new Semaphore(Math.max(1, properties.getMaxConcurrent()));
    }

    /**
     * 上传 Excel 并转换为 PDF 后上传 OSS；当前 CP2 不落库，只返回本次转换结果。
     */
    @Override
    public ExcelToPdfConversionResult convertExcelToPdf(String accessKey, MultipartFile file, String hotelId) {
        validateEnabled();
        validateAccessKey(accessKey);
        validateFile(file);
        String sourceFileName = safeFileName(file.getOriginalFilename(), "source.xlsx");
        acquirePermit();
        try {
            byte[] excelBytes = readFileBytes(file);
            validateFileSignature(sourceFileName, excelBytes);
            ExcelToPdfConvertedDocument converted = excelToPdfConverter.convert(new ExcelToPdfConversionInput(
                    sourceFileName,
                    file.getContentType(),
                    file.getSize(),
                    excelBytes));
            validateConvertedDocument(converted);
            ObjectStoragePutResult putResult = uploadPdf(hotelId, sourceFileName, converted);
            return new ExcelToPdfConversionResult(
                    DocumentConversionStatus.SUCCEEDED.name(),
                    sourceFileName,
                    file.getSize(),
                    converted.fileName(),
                    putResult.publicUrl(),
                    putResult.objectKey(),
                    putResult.contentType(),
                    putResult.sizeBytes(),
                    converted.durationMillis(),
                    null);
        } finally {
            conversionSemaphore.release();
        }
    }

    /**
     * 校验文件转换总开关。
     */
    private void validateEnabled() {
        if (!properties.isEnabled()) {
            throw new DocumentConversionException(
                    HttpStatus.NOT_FOUND,
                    "DOCUMENT_CONVERSION_DISABLED",
                    "文件转换接口未启用。");
        }
    }

    /**
     * 校验手动上传转换访问口令。
     */
    private void validateAccessKey(String accessKey) {
        if (properties.getAccessKey() == null || properties.getAccessKey().isBlank()
                || accessKey == null || !properties.getAccessKey().equals(accessKey)) {
            throw new DocumentConversionException(
                    HttpStatus.UNAUTHORIZED,
                    "DOCUMENT_CONVERSION_KEY_INVALID",
                    "文件转换访问口令缺失或错误。");
        }
    }

    /**
     * 校验上传文件类型、大小和扩展名。
     */
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_REQUEST,
                    "DOCUMENT_CONVERSION_FILE_REQUIRED",
                    "请上传 Excel 文件。");
        }
        if (file.getSize() > properties.getMaxFileBytes()) {
            throw new DocumentConversionException(
                    HttpStatus.PAYLOAD_TOO_LARGE,
                    "DOCUMENT_CONVERSION_FILE_TOO_LARGE",
                    "上传的 Excel 文件超过大小限制。");
        }
        String fileName = safeFileName(file.getOriginalFilename(), "");
        String lowerName = fileName.toLowerCase(Locale.ROOT);
        if (!lowerName.endsWith(".xlsx") && !lowerName.endsWith(".xls")) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_REQUEST,
                    "DOCUMENT_CONVERSION_FILE_TYPE_UNSUPPORTED",
                    "只支持上传 .xls 或 .xlsx 文件。");
        }
    }

    /**
     * 读取上传文件字节。
     */
    private byte[] readFileBytes(MultipartFile file) {
        try {
            return file.getBytes();
        } catch (Exception exception) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_REQUEST,
                    "DOCUMENT_CONVERSION_FILE_READ_FAILED",
                    "读取上传 Excel 文件失败。",
                    exception);
        }
    }

    /**
     * 校验 Excel 文件头，避免只改扩展名的任意内容进入 LibreOffice 解析器。
     */
    private void validateFileSignature(String sourceFileName, byte[] content) {
        String lowerName = sourceFileName.toLowerCase(Locale.ROOT);
        boolean valid;
        if (lowerName.endsWith(".xlsx")) {
            valid = content.length >= 4 && content[0] == 0x50 && content[1] == 0x4B;
        } else {
            valid = content.length >= 8
                    && (content[0] & 0xFF) == 0xD0
                    && (content[1] & 0xFF) == 0xCF
                    && (content[2] & 0xFF) == 0x11
                    && (content[3] & 0xFF) == 0xE0
                    && (content[4] & 0xFF) == 0xA1
                    && (content[5] & 0xFF) == 0xB1
                    && (content[6] & 0xFF) == 0x1A
                    && (content[7] & 0xFF) == 0xE1;
        }
        if (!valid) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_REQUEST,
                    "DOCUMENT_CONVERSION_FILE_CONTENT_INVALID",
                    "上传文件内容不是有效的 Excel 文件。");
        }
    }

    /**
     * 获取转换并发许可，避免大量文件同时触发 LibreOffice 进程。
     */
    private void acquirePermit() {
        if (!conversionSemaphore.tryAcquire()) {
            throw new DocumentConversionException(
                    HttpStatus.TOO_MANY_REQUESTS,
                    "DOCUMENT_CONVERSION_BUSY",
                    "文件转换任务繁忙，请稍后重试。");
        }
    }

    /**
     * 校验转换器返回的 PDF 内容。
     */
    private void validateConvertedDocument(ExcelToPdfConvertedDocument converted) {
        if (converted == null || converted.content() == null || converted.content().length == 0) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_FAILED",
                    "Excel 转 PDF 未生成有效文件。");
        }
    }

    /**
     * 上传 PDF 到 OSS，并把 OSS 失败转换为安全错误。
     */
    private ObjectStoragePutResult uploadPdf(
            String hotelId,
            String sourceFileName,
            ExcelToPdfConvertedDocument converted) {
        String pdfFileName = safeFileName(converted.fileName(), pdfFileName(sourceFileName));
        String objectKey = objectKey(hotelId, pdfFileName);
        try {
            return objectStorageService.putObject(new ObjectStoragePutRequest(
                    objectKey,
                    pdfFileName,
                    MediaType.APPLICATION_PDF_VALUE,
                    (long) converted.content().length,
                    converted.content()));
        } catch (RuntimeException exception) {
            throw new DocumentConversionException(
                    HttpStatus.BAD_GATEWAY,
                    "DOCUMENT_CONVERSION_OSS_UPLOAD_FAILED",
                    "PDF 上传 OSS 失败。",
                    exception);
        }
    }

    /**
     * 生成 PDF OSS 对象路径。
     */
    private String objectKey(String hotelId, String pdfFileName) {
        String prefix = properties.getOutputOssPrefix();
        String normalizedPrefix = prefix == null || prefix.isBlank()
                ? "document-conversions/excel-to-pdf/"
                : prefix;
        if (!normalizedPrefix.endsWith("/")) {
            normalizedPrefix = normalizedPrefix + "/";
        }
        String normalizedHotelId = safePathPart(hotelId == null || hotelId.isBlank() ? "UNKNOWN-HOTEL" : hotelId);
        String today = DATE_FORMATTER.format(LocalDate.now(ZoneOffset.UTC));
        return normalizedPrefix + normalizedHotelId + "/" + today + "/" + UUID.randomUUID() + "/" + pdfFileName;
    }

    /**
     * 根据 Excel 文件名生成 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;
    }

    /**
     * OSS 路径片段安全清洗。
     */
    private String safePathPart(String value) {
        String sanitized = value.replaceAll("[^A-Za-z0-9._-]", "_");
        return sanitized.isBlank() ? "UNKNOWN" : sanitized;
    }
}
