package com.lingniu.ingest.protocol.jt1078;

import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.identity.InMemoryVehicleIdentityService;
import com.lingniu.ingest.identity.VehicleIdentityBinding;
import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService;
import com.lingniu.ingest.protocol.jt1078.media.Jt1078RtpPacket;
import com.lingniu.ingest.sink.archive.ArchiveStore;
import com.lingniu.ingest.sink.archive.LocalArchiveStore;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

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

class Jt1078MediaArchiveServiceTest {

    @TempDir
    Path tempDir;

    @Test
    void archivesPayloadBySegmentAndPublishesMediaMetaOncePerSegment() throws Exception {
        LocalArchiveStore store = new LocalArchiveStore(tempDir.toString());
        List<VehicleEvent> published = new ArrayList<>();
        Jt1078MediaArchiveService service = new Jt1078MediaArchiveService(
                store, new InMemoryVehicleIdentityService(), published::add, 300);

        Jt1078RtpPacket p1 = new Jt1078RtpPacket(1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2});
        Jt1078RtpPacket p2 = new Jt1078RtpPacket(2, "013800138000", 3, 0, 1, 1_100L, new byte[]{3, 4, 5});
        service.archive(p1, "127.0.0.1:12345");
        service.archive(p2, "127.0.0.1:12345");

        assertThat(published).hasSize(1);
        VehicleEvent.MediaMeta meta = (VehicleEvent.MediaMeta) published.getFirst();
        assertThat(meta.source()).isEqualTo(ProtocolId.JT1078);
        assertThat(meta.vin()).isEqualTo("013800138000");
        assertThat(meta.sizeBytes()).isEqualTo(2);
        assertThat(meta.archiveRef()).startsWith("file://");
        assertThat(meta.metadata())
                .containsEntry("vin", "013800138000")
                .containsEntry("channelId", "3")
                .containsEntry("segment", "900")
                .containsEntry("segmentSizeBytes", "2")
                .containsEntry("archiveKey", "jt1078/1970/01/01/013800138000/ch-3/dt-0/900.media");

        Path archived = Path.of(meta.archiveRef().substring("file://".length()));
        assertThat(Files.readAllBytes(archived)).containsExactly(1, 2, 3, 4, 5);
    }

    @Test
    void archiveMediaMetaUsesResolvedInternalVinInMetadataAndArchiveKey() throws Exception {
        LocalArchiveStore store = new LocalArchiveStore(tempDir.toString());
        InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService();
        identity.bind(new VehicleIdentityBinding(
                ProtocolId.JT1078, "LNVIN107800000009", "013800138000", "", ""));
        List<VehicleEvent> published = new ArrayList<>();
        Jt1078MediaArchiveService service = new Jt1078MediaArchiveService(
                store, identity, published::add, 300);

        service.archive(new Jt1078RtpPacket(
                1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345");

        assertThat(published).singleElement()
                .isInstanceOf(VehicleEvent.MediaMeta.class)
                .satisfies(event -> {
                    assertThat(event.vin()).isEqualTo("LNVIN107800000009");
                    assertThat(event.metadata())
                            .containsEntry("vin", "LNVIN107800000009")
                            .containsEntry("sim", "013800138000")
                            .containsEntry("identityResolved", "true")
                            .containsEntry("archiveKey", "jt1078/1970/01/01/LNVIN107800000009/ch-3/dt-0/900.media");
                });
    }

    @Test
    void identityChangeWithinSameSegmentPublishesMediaMetaForNewArchiveKey() throws Exception {
        LocalArchiveStore store = new LocalArchiveStore(tempDir.toString());
        InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService();
        List<VehicleEvent> published = new ArrayList<>();
        Jt1078MediaArchiveService service = new Jt1078MediaArchiveService(
                store, identity, published::add, 300);

        service.archive(new Jt1078RtpPacket(
                1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345");
        identity.bind(new VehicleIdentityBinding(
                ProtocolId.JT1078, "LNVIN107800000009", "013800138000", "", ""));
        service.archive(new Jt1078RtpPacket(
                2, "013800138000", 3, 0, 1, 1_100L, new byte[]{3, 4}), "127.0.0.1:12345");

        assertThat(published)
                .extracting(event -> event.metadata().get("archiveKey"))
                .containsExactly(
                        "jt1078/1970/01/01/013800138000/ch-3/dt-0/900.media",
                        "jt1078/1970/01/01/LNVIN107800000009/ch-3/dt-0/900.media");
        assertThat(published)
                .extracting(VehicleEvent::vin)
                .containsExactly("013800138000", "LNVIN107800000009");
    }

    @Test
    void identityResolverFailureStillArchivesPayloadAndPublishesMediaMeta() throws Exception {
        LocalArchiveStore store = new LocalArchiveStore(tempDir.toString());
        List<VehicleEvent> published = new ArrayList<>();
        Jt1078MediaArchiveService service = new Jt1078MediaArchiveService(
                store,
                lookup -> {
                    throw new IllegalStateException("identity backend unavailable");
                },
                published::add,
                300);

        service.archive(new Jt1078RtpPacket(
                1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345");

        assertThat(published).singleElement()
                .isInstanceOf(VehicleEvent.MediaMeta.class)
                .satisfies(event -> {
                    VehicleEvent.MediaMeta media = (VehicleEvent.MediaMeta) event;
                    assertThat(media.vin()).isEqualTo("unknown");
                    assertThat(media.archiveRef()).startsWith("file://");
                    assertThat(media.metadata())
                            .containsEntry("vin", "unknown")
                            .containsEntry("sim", "013800138000")
                            .containsEntry("identityResolved", "false")
                            .containsEntry("identitySource", "UNKNOWN")
                            .containsEntry("identityError", "true")
                            .containsEntry("identityErrorMessage", "identity backend unavailable")
                            .containsEntry("archiveKey", "jt1078/1970/01/01/unknown/ch-3/dt-0/900.media");
                    Path archived = Path.of(media.archiveRef().substring("file://".length()));
                    assertThat(Files.readAllBytes(archived)).containsExactly(1, 2);
                });
    }

    @Test
    void archiveFailurePublishesPassthroughErrorEventWithOriginalPayload() {
        List<VehicleEvent> published = new ArrayList<>();
        Jt1078MediaArchiveService service = new Jt1078MediaArchiveService(
                new FailingArchiveStore(), new InMemoryVehicleIdentityService(), published::add, 300);

        Jt1078RtpPacket packet = new Jt1078RtpPacket(
                9, "013800138000", 4, 2, 1, 1_000L, new byte[]{9, 8, 7});

        service.archive(packet, "127.0.0.1:9000");

        assertThat(published).singleElement()
                .satisfies(event -> {
                    assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class);
                    VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event;
                    assertThat(passthrough.source()).isEqualTo(ProtocolId.JT1078);
                    assertThat(passthrough.vin()).isEqualTo("013800138000");
                    assertThat(passthrough.passthroughType()).isEqualTo(2);
                    assertThat(passthrough.data()).containsExactly(9, 8, 7);
                    assertThat(passthrough.metadata())
                            .containsEntry("archiveError", "true")
                            .containsEntry("vin", "013800138000")
                            .containsEntry("sim", "013800138000")
                            .containsEntry("channelId", "4")
                            .containsEntry("sequence", "9")
                            .containsEntry("peer", "127.0.0.1:9000")
                            .containsEntry("segment", "900")
                            .containsEntry("archiveKey", "jt1078/1970/01/01/013800138000/ch-4/dt-2/900.media");
                });
    }

    private static final class FailingArchiveStore implements ArchiveStore {
        @Override
        public String put(String key, InputStream data, long length) throws IOException {
            throw new IOException("archive backend unavailable");
        }

        @Override
        public String append(String key, byte[] chunk) throws IOException {
            throw new IOException("archive backend unavailable");
        }

        @Override
        public InputStream get(String key) throws IOException {
            throw new IOException("archive backend unavailable");
        }

        @Override
        public boolean exists(String key) {
            return false;
        }

        @Override
        public long size(String key) throws IOException {
            throw new IOException("archive backend unavailable");
        }
    }
}
