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.impl.DocumentConversionProperties;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.http.HttpStatus;

class LibreOfficeExcelToPdfAdapterTest {

    @TempDir
    private Path tempDir;

    @Test
    void shouldInvokeLibreOfficeHeadlessAndCleanTemporaryFiles() throws Exception {
        DocumentConversionProperties properties = properties();
        AtomicReference<LibreOfficeProcessRequest> processRequestRef = new AtomicReference<>();
        LibreOfficeProcessRunner processRunner = request -> {
            processRequestRef.set(request);
            org.assertj.core.api.Assertions.assertThat(request.command())
                    .contains("soffice-test")
                    .contains("--headless")
                    .contains("--convert-to")
                    .contains("pdf");
            org.assertj.core.api.Assertions.assertThat(Files.exists(request.inputFile())).isTrue();
            writePdf(request.outputDirectory().resolve("booking-request.pdf"));
            return new LibreOfficeProcessResult(0, false, "convert ok", "");
        };
        LibreOfficeExcelToPdfAdapter adapter = new LibreOfficeExcelToPdfAdapter(properties, processRunner);

        ExcelToPdfConvertedDocument result = adapter.convert(new ExcelToPdfConversionInput(
                "booking-request.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                10L,
                "xlsx-bytes".getBytes(StandardCharsets.UTF_8)));

        org.assertj.core.api.Assertions.assertThat(result.fileName()).isEqualTo("booking-request.pdf");
        org.assertj.core.api.Assertions.assertThat(result.contentType()).isEqualTo("application/pdf");
        org.assertj.core.api.Assertions.assertThat(result.content()).startsWith("%PDF".getBytes(StandardCharsets.UTF_8));
        org.assertj.core.api.Assertions.assertThat(processRequestRef.get().profileDirectory().toString())
                .contains("lo-profile");
        org.assertj.core.api.Assertions.assertThat(processRequestRef.get().workingDirectory())
                .isEqualTo(processRequestRef.get().inputFile().getParent());
        try (var children = Files.list(tempDir)) {
            org.assertj.core.api.Assertions.assertThat(children).isEmpty();
        }
    }

    @Test
    void shouldReturnTimeoutErrorAndCleanTemporaryFiles() {
        DocumentConversionProperties properties = properties();
        LibreOfficeProcessRunner processRunner = request -> new LibreOfficeProcessResult(
                -1,
                true,
                "",
                "timeout");
        LibreOfficeExcelToPdfAdapter adapter = new LibreOfficeExcelToPdfAdapter(properties, processRunner);

        org.assertj.core.api.Assertions.assertThatThrownBy(() -> adapter.convert(new ExcelToPdfConversionInput(
                        "booking-request.xlsx",
                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                        10L,
                        "xlsx-bytes".getBytes(StandardCharsets.UTF_8))))
                .isInstanceOf(DocumentConversionException.class)
                .satisfies(exception -> {
                    DocumentConversionException conversionException = (DocumentConversionException) exception;
                    org.assertj.core.api.Assertions.assertThat(conversionException.getStatus())
                            .isEqualTo(HttpStatus.GATEWAY_TIMEOUT);
                    org.assertj.core.api.Assertions.assertThat(conversionException.getErrorCode())
                            .isEqualTo("DOCUMENT_CONVERSION_TIMEOUT");
                });
        assertTempDirEmpty();
    }

    @Test
    void shouldReturnFailedErrorWhenLibreOfficeDoesNotProducePdf() {
        DocumentConversionProperties properties = properties();
        LibreOfficeProcessRunner processRunner = request -> new LibreOfficeProcessResult(0, false, "ok", "");
        LibreOfficeExcelToPdfAdapter adapter = new LibreOfficeExcelToPdfAdapter(properties, processRunner);

        org.assertj.core.api.Assertions.assertThatThrownBy(() -> adapter.convert(new ExcelToPdfConversionInput(
                        "booking-request.xlsx",
                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                        10L,
                        "xlsx-bytes".getBytes(StandardCharsets.UTF_8))))
                .isInstanceOf(DocumentConversionException.class)
                .satisfies(exception -> {
                    DocumentConversionException conversionException = (DocumentConversionException) exception;
                    org.assertj.core.api.Assertions.assertThat(conversionException.getStatus())
                            .isEqualTo(HttpStatus.BAD_GATEWAY);
                    org.assertj.core.api.Assertions.assertThat(conversionException.getErrorCode())
                            .isEqualTo("DOCUMENT_CONVERSION_FAILED");
                });
        assertTempDirEmpty();
    }

    private DocumentConversionProperties properties() {
        DocumentConversionProperties properties = new DocumentConversionProperties();
        properties.setEnabled(true);
        properties.setSofficePath("soffice-test");
        properties.setTempDir(tempDir.toString());
        properties.setTimeoutSeconds(3);
        properties.setOutputOssPrefix("document-conversions/excel-to-pdf/");
        return properties;
    }

    private void writePdf(Path pdfFile) {
        try {
            Files.write(pdfFile, "%PDF-1.7\nconverted".getBytes(StandardCharsets.UTF_8));
        } catch (Exception exception) {
            throw new AssertionError("写入模拟 PDF 失败。", exception);
        }
    }

    private void assertTempDirEmpty() {
        try (var children = Files.list(tempDir)) {
            org.assertj.core.api.Assertions.assertThat(children).isEmpty();
        } catch (Exception exception) {
            throw new AssertionError("临时目录检查失败。", exception);
        }
    }
}
