package org.raddatz.familienarchiv.ocr;

import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

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

class RestClientOcrClientStreamTest {

    @Test
    void parseNdjsonStream_dispatchesStartPageDoneInOrder() {
        String ndjson = """
                {"type":"start","totalPages":2}
                {"type":"page","pageNumber":0,"blocks":[{"pageNumber":0,"x":0.1,"y":0.2,"width":0.8,"height":0.1,"polygon":null,"text":"Line 1"}]}
                {"type":"page","pageNumber":1,"blocks":[{"pageNumber":1,"x":0.1,"y":0.3,"width":0.8,"height":0.1,"polygon":null,"text":"Line 2"}]}
                {"type":"done","totalBlocks":2,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(4);
        assertThat(events.get(0)).isInstanceOf(OcrStreamEvent.Start.class);
        assertThat(((OcrStreamEvent.Start) events.get(0)).totalPages()).isEqualTo(2);

        assertThat(events.get(1)).isInstanceOf(OcrStreamEvent.Page.class);
        var page0 = (OcrStreamEvent.Page) events.get(1);
        assertThat(page0.pageNumber()).isEqualTo(0);
        assertThat(page0.blocks()).hasSize(1);
        assertThat(page0.blocks().get(0).text()).isEqualTo("Line 1");

        assertThat(events.get(2)).isInstanceOf(OcrStreamEvent.Page.class);
        var page1 = (OcrStreamEvent.Page) events.get(2);
        assertThat(page1.pageNumber()).isEqualTo(1);

        assertThat(events.get(3)).isInstanceOf(OcrStreamEvent.Done.class);
        var done = (OcrStreamEvent.Done) events.get(3);
        assertThat(done.totalBlocks()).isEqualTo(2);
        assertThat(done.skippedPages()).isEqualTo(0);
    }

    @Test
    void parseNdjsonStream_parsesErrorEvents() {
        String ndjson = """
                {"type":"start","totalPages":3}
                {"type":"page","pageNumber":0,"blocks":[]}
                {"type":"error","pageNumber":1,"message":"OCR processing failed on page 1"}
                {"type":"page","pageNumber":2,"blocks":[]}
                {"type":"done","totalBlocks":0,"skippedPages":1}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(5);
        assertThat(events.get(2)).isInstanceOf(OcrStreamEvent.Error.class);
        var error = (OcrStreamEvent.Error) events.get(2);
        assertThat(error.pageNumber()).isEqualTo(1);
        assertThat(error.message()).contains("OCR processing failed");
    }

    @Test
    void parseNdjsonStream_skipsBlankLines() {
        String ndjson = """
                {"type":"start","totalPages":1}

                {"type":"page","pageNumber":0,"blocks":[]}

                {"type":"done","totalBlocks":0,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(3);
    }

    @Test
    void parseNdjsonStream_dispatchesPreprocessingEvent() {
        String ndjson = """
                {"type":"start","totalPages":3}
                {"type":"preprocessing","pageNumber":1}
                {"type":"page","pageNumber":1,"blocks":[]}
                {"type":"done","totalBlocks":0,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(4);
        assertThat(events.get(1)).isInstanceOf(OcrStreamEvent.Preprocessing.class);
        var preprocessing = (OcrStreamEvent.Preprocessing) events.get(1);
        assertThat(preprocessing.pageNumber()).isEqualTo(1);
    }

    @Test
    void parseNdjsonStream_ignoresUnknownEventTypes() {
        String ndjson = """
                {"type":"start","totalPages":1}
                {"type":"unknown","foo":"bar"}
                {"type":"done","totalBlocks":0,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(2);
    }

    @Test
    void parseNdjsonStream_handlesUnknownFieldsInBlocks() {
        String ndjson = """
                {"type":"start","totalPages":1}
                {"type":"page","pageNumber":0,"blocks":[{"pageNumber":0,"x":0.1,"y":0.2,"width":0.8,"height":0.1,"polygon":null,"text":"Line 1","confidence":0.95,"newFutureField":"ignored"}]}
                {"type":"done","totalBlocks":1,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        assertThat(events).hasSize(3);
        var page = (OcrStreamEvent.Page) events.get(1);
        assertThat(page.blocks().get(0).text()).isEqualTo("Line 1");
    }

    @Test
    void parseNdjsonStream_parsesPageWithPolygon() {
        String ndjson = """
                {"type":"start","totalPages":1}
                {"type":"page","pageNumber":0,"blocks":[{"pageNumber":0,"x":0.1,"y":0.2,"width":0.8,"height":0.1,"polygon":[[0.1,0.2],[0.9,0.2],[0.9,0.3],[0.1,0.3]],"text":"With polygon"}]}
                {"type":"done","totalBlocks":1,"skippedPages":0}
                """;
        InputStream stream = new ByteArrayInputStream(ndjson.getBytes(StandardCharsets.UTF_8));

        List<OcrStreamEvent> events = new ArrayList<>();
        RestClientOcrClient.parseNdjsonStream(stream, events::add);

        var page = (OcrStreamEvent.Page) events.get(1);
        assertThat(page.blocks().get(0).polygon()).hasSize(4);
        assertThat(page.blocks().get(0).text()).isEqualTo("With polygon");
    }
}
