package com.lingniu.ingest.sink.archive;

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.RawArchiveKeys;
import com.lingniu.ingest.api.event.VehicleEvent;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Map;

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

class RawArchiveEventSinkTest {

    @TempDir
    Path tempDir;

    @Test
    void writesRawBytesUsingSharedArchiveKey() throws Exception {
        RawArchiveEventSink sink = new RawArchiveEventSink(tempDir);
        VehicleEvent.RawArchive raw = rawArchive(Map.of(), new byte[]{0x23, 0x23, 0x01});

        sink.publish(raw).join();

        String key = RawArchiveKeys.key(raw);
        assertThat(Files.readAllBytes(tempDir.resolve(key))).containsExactly(0x23, 0x23, 0x01);
        assertThat(RawArchiveKeys.logicalUri(key)).isEqualTo("archive://" + key);
    }

    @Test
    void rejectsArchiveKeysThatEscapeRoot() {
        RawArchiveEventSink sink = new RawArchiveEventSink(tempDir);
        VehicleEvent.RawArchive raw = rawArchive(
                Map.of(RawArchiveKeys.META_KEY, "../escape.bin"),
                new byte[]{0x01});

        assertThatThrownBy(() -> sink.publish(raw).join())
                .hasCauseInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("raw archive key escapes root");
    }

    @Test
    void rejectsSymlinkTraversalBeforeCreatingDirectories() throws Exception {
        Path root = tempDir.resolve("archive");
        Path outside = tempDir.resolve("outside");
        Files.createDirectories(root);
        Files.createDirectories(outside);
        Files.createSymbolicLink(root.resolve("link"), outside);

        RawArchiveEventSink sink = new RawArchiveEventSink(root);
        VehicleEvent.RawArchive raw = rawArchive(
                Map.of(RawArchiveKeys.META_KEY, "link/nested/raw.bin"),
                new byte[]{0x01});

        assertThatThrownBy(() -> sink.publish(raw).join())
                .hasRootCauseMessage("raw archive path traverses symlink: " + root.resolve("link"));
        assertThat(outside.resolve("nested")).doesNotExist();
    }

    private static VehicleEvent.RawArchive rawArchive(Map<String, String> metadata, byte[] rawBytes) {
        return new VehicleEvent.RawArchive(
                "raw-1",
                "VIN001",
                ProtocolId.GB32960,
                Instant.parse("2026-06-22T08:00:00Z"),
                Instant.parse("2026-06-22T08:00:01Z"),
                "trace-1",
                metadata,
                0x02,
                0,
                rawBytes);
    }
}
