package incheon.sgp.thm.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;

/**
 * SgpThm 공용 NDJSON(Newline Delimited JSON) 스트리밍 핸들러
 * 
 * MyBatis ResultHandler를 구현하여 DB 커서 기반으로
 * 대용량 데이터를 줄 단위로 스트리밍 출력합니다.
 * 
 * 기능:
 * - 기본 모드: DTO를 그대로 JSON 직렬화
 * - 압축 모드: transformer 함수로 변환 후 직렬화 (용량 절감)
 * 
 * 사용 예시:
 * - 비오톱 멀티폴리곤 (58,000+ 건)
 * - 선거구 현황 전체 외곽선
 * - 기타 대용량 GeoJSON 데이터
 * 
 * @param <T> 스트리밍할 DTO 타입
 */
@Slf4j
public class SgpThmNdjsonHandler<T> implements ResultHandler<T> {

    /** Progressive Rendering을 위한 Flush 간격 (건 수) */
    private static final int DEFAULT_FLUSH_INTERVAL = 500;

    private final OutputStream outputStream;
    private final ObjectMapper mapper;
    private final int flushInterval;
    private final Function<T, Object> transformer;

    private long count = 0;
    private boolean hasError = false;

    /**
     * 기본 생성자 (500건마다 flush, 변환 없음)
     * 
     * @param outputStream 출력 스트림
     * @param mapper       JSON 직렬화용 ObjectMapper
     */
    public SgpThmNdjsonHandler(OutputStream outputStream, ObjectMapper mapper) {
        this(outputStream, mapper, DEFAULT_FLUSH_INTERVAL, null);
    }

    /**
     * 커스텀 flush 간격 생성자 (변환 없음)
     * 
     * @param outputStream  출력 스트림
     * @param mapper        JSON 직렬화용 ObjectMapper
     * @param flushInterval flush 간격 (건 수)
     */
    public SgpThmNdjsonHandler(OutputStream outputStream, ObjectMapper mapper, int flushInterval) {
        this(outputStream, mapper, flushInterval, null);
    }

    /**
     * 압축 변환 지원 생성자
     * 
     * @param outputStream  출력 스트림
     * @param mapper        JSON 직렬화용 ObjectMapper
     * @param flushInterval flush 간격 (건 수)
     * @param transformer   DTO를 압축 배열로 변환하는 함수 (null이면 변환 없이 DTO 그대로 출력)
     */
    public SgpThmNdjsonHandler(OutputStream outputStream, ObjectMapper mapper,
            int flushInterval, Function<T, Object> transformer) {
        this.outputStream = outputStream;
        this.mapper = mapper;
        this.flushInterval = flushInterval > 0 ? flushInterval : DEFAULT_FLUSH_INTERVAL;
        this.transformer = transformer;
    }

    @Override
    public void handleResult(ResultContext<? extends T> context) {
        if (hasError) {
            context.stop();
            return;
        }

        try {
            // 첫 row 도착 시간 측정
            if (count == 0) {
                log.info("NDJSON first row received at {}ms", System.currentTimeMillis() % 100000);
            }

            T dto = context.getResultObject();

            // transformer가 있으면 변환, 없으면 DTO 그대로 사용
            Object outputObject = (transformer != null) ? transformer.apply(dto) : dto;

            String jsonLine = mapper.writeValueAsString(outputObject) + "\n";
            outputStream.write(jsonLine.getBytes(StandardCharsets.UTF_8));
            count++;

            // 지정된 간격마다 flush (Progressive Rendering 지원)
            // 첫 100건에서도 조기 flush하여 TTFB 개선
            if (count == 100 || count % flushInterval == 0) {
                outputStream.flush();
                if (log.isDebugEnabled()) {
                    log.debug("NDJSON stream flushed at {} rows", count);
                }
            }
        } catch (IOException e) {
            log.error("NDJSON write error at row {}: {}", count, e.getMessage());
            hasError = true;
            context.stop();
        }
    }

    /**
     * 스트리밍된 총 row 수 반환
     * 
     * @return 총 row 수
     */
    public long getCount() {
        return count;
    }

    /**
     * 에러 발생 여부 확인
     * 
     * @return 에러 발생 시 true
     */
    public boolean hasError() {
        return hasError;
    }
}
