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.ExcelToPdfConvertedDocument;
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.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;

class DocumentConversionServiceImplTest {

    @Test
    void shouldRejectSecondConversionWhenConcurrencyLimitReachedBeforeReadingBytes() throws Exception {
        byte[] pdfBytes = "%PDF-1.7".getBytes(StandardCharsets.UTF_8);
        CountDownLatch converterEntered = new CountDownLatch(1);
        CountDownLatch releaseConverter = new CountDownLatch(1);
        ExcelToPdfConverter converter = input -> {
            converterEntered.countDown();
            await(releaseConverter);
            return new ExcelToPdfConvertedDocument(
                    "booking-request.pdf",
                    MediaType.APPLICATION_PDF_VALUE,
                    (long) pdfBytes.length,
                    pdfBytes,
                    10L);
        };
        ObjectStorageService storageService = request -> putResult(request);
        DocumentConversionService service = new DocumentConversionServiceImpl(properties(), converter, storageService);

        Thread firstThread = new Thread(() -> service.convertExcelToPdf(
                "test-document-key",
                excelFile("booking-request.xlsx"),
                "HOTEL-TEST"));
        firstThread.start();
        org.assertj.core.api.Assertions.assertThat(converterEntered.await(2, TimeUnit.SECONDS)).isTrue();

        org.assertj.core.api.Assertions.assertThatThrownBy(() -> service.convertExcelToPdf(
                        "test-document-key",
                        excelFile("second-request.xlsx"),
                        "HOTEL-TEST"))
                .isInstanceOf(DocumentConversionException.class)
                .satisfies(exception -> {
                    DocumentConversionException conversionException = (DocumentConversionException) exception;
                    org.assertj.core.api.Assertions.assertThat(conversionException.getErrorCode())
                            .isEqualTo("DOCUMENT_CONVERSION_BUSY");
                });

        releaseConverter.countDown();
        firstThread.join(2000);
    }

    private DocumentConversionProperties properties() {
        DocumentConversionProperties properties = new DocumentConversionProperties();
        properties.setEnabled(true);
        properties.setAccessKey("test-document-key");
        properties.setMaxConcurrent(1);
        properties.setMaxFileBytes(1024);
        properties.setOutputOssPrefix("document-conversions/excel-to-pdf/");
        return properties;
    }

    private MockMultipartFile excelFile(String fileName) {
        return new MockMultipartFile(
                "file",
                fileName,
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                xlsxBytes(fileName));
    }

    private byte[] xlsxBytes(String content) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            outputStream.write(new byte[]{0x50, 0x4B, 0x03, 0x04});
            outputStream.write(content.getBytes(StandardCharsets.UTF_8));
            return outputStream.toByteArray();
        } catch (Exception exception) {
            throw new AssertionError("构造测试 xlsx 字节失败。", exception);
        }
    }

    private ObjectStoragePutResult putResult(ObjectStoragePutRequest request) {
        return new ObjectStoragePutResult(
                request.objectKey(),
                "https://oss.example.test/" + request.objectKey(),
                request.contentType(),
                request.sizeBytes());
    }

    private void await(CountDownLatch latch) {
        try {
            org.assertj.core.api.Assertions.assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue();
        } catch (InterruptedException exception) {
            Thread.currentThread().interrupt();
            throw new AssertionError("等待转换器释放失败。", exception);
        }
    }
}
