package com.lingniu.ingest.sink.archive;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;

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

class LocalArchiveStoreTest {

    @TempDir
    Path tempDir;

    @Test
    void rejectsSymlinkTraversalOutsideRootForWriteReadAndSize() throws Exception {
        Path root = tempDir.resolve("archive");
        Path outside = tempDir.resolve("outside");
        Files.createDirectories(root);
        Files.createDirectories(outside);
        Files.writeString(outside.resolve("existing.bin"), "secret", StandardCharsets.UTF_8);
        Files.createSymbolicLink(root.resolve("link"), outside);

        LocalArchiveStore store = new LocalArchiveStore(root);

        assertThatThrownBy(() -> store.put(
                "link/new.bin",
                new ByteArrayInputStream("payload".getBytes(StandardCharsets.UTF_8)),
                7))
                .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class);
        assertThat(outside.resolve("new.bin")).doesNotExist();

        assertThatThrownBy(() -> store.get("link/existing.bin"))
                .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class);
        assertThatThrownBy(() -> store.size("link/existing.bin"))
                .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class);
    }

    @Test
    void appendsConcurrentChunksWithoutLosingData() throws Exception {
        LocalArchiveStore store = new LocalArchiveStore(tempDir.resolve("archive"));
        int chunks = 96;

        try (var executor = Executors.newFixedThreadPool(12)) {
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            for (int i = 0; i < chunks; i++) {
                String chunk = "%03d\n".formatted(i);
                futures.add(CompletableFuture.runAsync(() -> {
                    try {
                        store.append("same/key.bin", chunk.getBytes(StandardCharsets.UTF_8));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }, executor));
            }
            CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();
        }

        String content = Files.readString(tempDir.resolve("archive/same/key.bin"), StandardCharsets.UTF_8);
        assertThat(content).hasSize(chunks * 4);
        assertThat(content.lines()).containsExactlyInAnyOrderElementsOf(
                java.util.stream.IntStream.range(0, chunks)
                        .mapToObj("%03d"::formatted)
                        .toList());
    }
}
