package org.raddatz.familienarchiv.document;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.PostgresContainerConfig;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.DocumentStatus;
import org.raddatz.familienarchiv.document.DocumentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Full round-trip integration test against real MinIO and real Postgres. Catches S3
 * signing / presigning issues that a mocked S3Client would miss — the rest of the
 * test pyramid mocks at the FileService boundary.
 */
@SpringBootTest
@ActiveProfiles("test")
@Import(PostgresContainerConfig.class)
class ThumbnailServiceIntegrationTest {

    private static final String BUCKET = "archive-documents";
    private static final String ACCESS_KEY = "minioadmin";
    private static final String SECRET_KEY = "minioadmin";

    static GenericContainer<?> minio = new GenericContainer<>("minio/minio:RELEASE.2024-06-13T22-53-53Z")
            .withEnv("MINIO_ROOT_USER", ACCESS_KEY)
            .withEnv("MINIO_ROOT_PASSWORD", SECRET_KEY)
            .withCommand("server /data")
            .withExposedPorts(9000);

    static {
        minio.start();
    }

    @DynamicPropertySource
    static void s3Properties(DynamicPropertyRegistry registry) {
        registry.add("app.s3.endpoint", () -> "http://" + minio.getHost() + ":" + minio.getMappedPort(9000));
        registry.add("app.s3.access-key", () -> ACCESS_KEY);
        registry.add("app.s3.secret-key", () -> SECRET_KEY);
        registry.add("app.s3.bucket", () -> BUCKET);
        registry.add("app.s3.region", () -> "eu-central-1");
    }

    @Autowired S3Client s3Client;
    @Autowired ThumbnailService thumbnailService;
    @Autowired DocumentRepository documentRepository;

    @Test
    void generate_writesDecodableJpegToMinio_readbackMatches() throws IOException {
        // Ensure bucket exists (the real app has a bootstrap container for this; in tests we do it here).
        // Re-creating is a no-op; wrap in try/catch because the SDK throws on "already owned".
        try (S3Client bootstrap = buildClient()) {
            try {
                bootstrap.createBucket(CreateBucketRequest.builder().bucket(BUCKET).build());
            } catch (Exception ignored) {
                // already exists
            }
        }

        // Persist first so Hibernate assigns the UUID — avoids StaleObjectState on a pre-set id
        Document persisted = documentRepository.save(Document.builder()
                .title("IT Doc")
                .originalFilename("test.pdf")
                .status(DocumentStatus.UPLOADED)
                .contentType("application/pdf")
                .build());
        UUID docId = persisted.getId();
        String pdfKey = "documents/" + docId + "_test.pdf";

        s3Client.putObject(PutObjectRequest.builder()
                        .bucket(BUCKET)
                        .key(pdfKey)
                        .contentType("application/pdf")
                        .build(),
                RequestBody.fromBytes(createSamplePdf()));

        persisted.setFilePath(pdfKey);
        persisted = documentRepository.save(persisted);

        ThumbnailService.Outcome outcome = thumbnailService.generate(persisted);

        assertThat(outcome).isEqualTo(ThumbnailService.Outcome.SUCCESS);

        Document reloaded = documentRepository.findById(docId).orElseThrow();
        assertThat(reloaded.getThumbnailKey()).isEqualTo("thumbnails/" + docId + ".jpg");
        assertThat(reloaded.getThumbnailGeneratedAt()).isNotNull();

        // Read back from MinIO and verify it decodes as a JPEG of the expected width
        try (InputStream in = s3Client.getObject(GetObjectRequest.builder()
                .bucket(BUCKET).key(reloaded.getThumbnailKey()).build())) {
            byte[] jpegBytes = in.readAllBytes();
            BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(jpegBytes));
            assertThat(decoded).isNotNull();
            assertThat(decoded.getWidth()).isEqualTo(240);
        }
    }

    private static S3Client buildClient() {
        return S3Client.builder()
                .endpointOverride(URI.create("http://" + minio.getHost() + ":" + minio.getMappedPort(9000)))
                .serviceConfiguration(S3Configuration.builder().pathStyleAccessEnabled(true).build())
                .region(Region.of("eu-central-1"))
                .credentialsProvider(StaticCredentialsProvider.create(
                        AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY)))
                .build();
    }

    private static byte[] createSamplePdf() throws IOException {
        try (PDDocument pdf = new PDDocument()) {
            pdf.addPage(new PDPage(PDRectangle.A4));
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            pdf.save(bos);
            return bos.toByteArray();
        }
    }
}
