package incheon.product.geoview3d.traffic.handler;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;

/**
 * NdjsonStreamHandler 단위 테스트.
 * NDJSON 형식(행 단위 JSON + 개행), flush 인터벌, 변환 함수 적용을 검증한다.
 */
class NdjsonStreamHandlerTest {

    @Test
    @DisplayName("단일 행 — JSON + 개행 문자로 출력")
    void writeSingleRow() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        NdjsonStreamHandler<Map<String, Object>> handler = new NdjsonStreamHandler<>(out, row -> row);

        handler.handle(Map.of("id", 1, "name", "test"));
        handler.finish();

        String result = out.toString(StandardCharsets.UTF_8);
        assertThat(result).endsWith("\n");
        assertThat(result.trim()).contains("\"id\"").contains("\"name\"");
    }

    @Test
    @DisplayName("복수 행 — 각 행이 개행으로 구분")
    void writeMultipleRows() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        NdjsonStreamHandler<String> handler = new NdjsonStreamHandler<>(out, s -> Map.of("v", s));

        handler.handle("a");
        handler.handle("b");
        handler.handle("c");
        handler.finish();

        String result = out.toString(StandardCharsets.UTF_8);
        String[] lines = result.split("\n");
        assertThat(lines).hasSize(3);
        assertThat(lines[0]).contains("\"v\":\"a\"");
        assertThat(lines[2]).contains("\"v\":\"c\"");
    }

    @Test
    @DisplayName("변환 함수(transformer) 적용 확인")
    void transformerApplied() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // Integer → {value: n, doubled: n*2} 변환
        NdjsonStreamHandler<Integer> handler = new NdjsonStreamHandler<>(out,
                n -> Map.of("value", n, "doubled", n * 2));

        handler.handle(5);
        handler.finish();

        String result = out.toString(StandardCharsets.UTF_8).trim();
        assertThat(result).contains("\"value\":5").contains("\"doubled\":10");
    }

    @Test
    @DisplayName("500행마다 flush 호출 확인")
    void flushIntervalAt500() throws IOException {
        CountingOutputStream out = new CountingOutputStream();
        NdjsonStreamHandler<Integer> handler = new NdjsonStreamHandler<>(out, n -> Map.of("n", n));

        // 499행 → flush 0회 (handle 내부), 500행 → flush 1회
        for (int i = 1; i <= 500; i++) {
            handler.handle(i);
        }

        assertThat(out.flushCount).isEqualTo(1);

        // 1000행 → flush 2회
        for (int i = 501; i <= 1000; i++) {
            handler.handle(i);
        }

        assertThat(out.flushCount).isEqualTo(2);
    }

    @Test
    @DisplayName("finish() 호출 시 잔여 데이터 flush")
    void finishFlushesRemaining() throws IOException {
        CountingOutputStream out = new CountingOutputStream();
        NdjsonStreamHandler<Integer> handler = new NdjsonStreamHandler<>(out, n -> Map.of("n", n));

        handler.handle(1);
        int beforeFinish = out.flushCount;
        handler.finish();

        assertThat(out.flushCount).isEqualTo(beforeFinish + 1);
    }

    /** flush 횟수를 추적하는 OutputStream */
    private static class CountingOutputStream extends ByteArrayOutputStream {
        int flushCount = 0;

        @Override
        public void flush() throws IOException {
            super.flush();
            flushCount++;
        }
    }
}
