package com.dexorder.flink.sink;

import com.dexorder.flink.publisher.OHLCBatchWrapper;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.table.data.GenericRowData;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.data.StringData;
import org.apache.flink.types.RowKind;
import org.apache.flink.util.Collector;
import org.apache.iceberg.flink.TableLoader;
import org.apache.iceberg.flink.sink.FlinkSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Iceberg sink for OHLC data (Iceberg 1.10.1).
 * Converts OHLCBatchWrapper to Flink RowData and writes to single Iceberg table.
 *
 * Deduplication Strategy:
 * - Uses Flink's upsert mode with equality delete files
 * - Natural key: (ticker, period_seconds, timestamp)
 * - Last-write-wins semantics for duplicates
 * - Copy-on-write mode for better query performance
 */
public class IcebergOHLCSink {
    private static final Logger LOG = LoggerFactory.getLogger(IcebergOHLCSink.class);

    /**
     * Create an Iceberg sink for OHLC data with upsert behavior.
     *
     * @param stream Input stream of OHLCBatchWrapper
     * @param tableLoader Iceberg table loader for trading.ohlc
     * @return DataStream with sink applied
     */
    public static DataStream<RowData> createSink(
            DataStream<OHLCBatchWrapper> stream,
            TableLoader tableLoader
    ) {
        // Convert OHLCBatchWrapper to RowData
        DataStream<RowData> rowStream = stream
                .flatMap(new OHLCBatchToRowDataMapper())
                .name("OHLCBatch to RowData");

        // Apply Iceberg sink with upsert mode
        // Upsert uses equality delete files to handle duplicates
        // Natural key is (ticker, period_seconds, timestamp)
        FlinkSink.forRowData(rowStream)
                .tableLoader(tableLoader)
                .upsert(true)  // Enables equality delete file generation
                .equalityFieldColumns(java.util.Arrays.asList("ticker", "period_seconds", "timestamp"))
                .append();

        LOG.info("Iceberg OHLC sink configured with upsert mode (equality deletes on ticker, period_seconds, timestamp)");

        return rowStream;
    }

    /**
     * Mapper that converts OHLCBatchWrapper to Flink RowData.
     * Flattens the batch into individual rows for Iceberg.
     */
    private static class OHLCBatchToRowDataMapper implements FlatMapFunction<OHLCBatchWrapper, RowData> {
        private static final long serialVersionUID = 1L;

        @Override
        public void flatMap(OHLCBatchWrapper batch, Collector<RowData> out) throws Exception {
            // Skip empty batches (marker messages)
            if (batch.getRows() == null || batch.getRows().isEmpty()) {
                LOG.debug("Skipping empty batch (marker): request_id={}, status={}",
                        batch.getRequestId(), batch.getStatus());
                return;
            }

            String requestId = batch.getRequestId();
            String ticker = batch.getTicker();
            int periodSeconds = batch.getPeriodSeconds();
            long ingestedAt = System.currentTimeMillis() * 1000;

            // Emit one RowData for each OHLC row in the batch
            for (OHLCBatchWrapper.OHLCRow row : batch.getRows()) {
                GenericRowData rowData = new GenericRowData(RowKind.INSERT, 17);

                // Natural key fields (ticker, period_seconds, timestamp)
                // Used by equality delete files for deduplication
                rowData.setField(0, StringData.fromString(ticker));
                rowData.setField(1, periodSeconds);
                rowData.setField(2, row.getTimestamp());

                // OHLC price data
                rowData.setField(3, row.getOpen());
                rowData.setField(4, row.getHigh());
                rowData.setField(5, row.getLow());
                rowData.setField(6, row.getClose());

                // Volume data
                rowData.setField(7, row.getVolume());
                rowData.setField(8, null);  // buy_vol (TODO: extract from protobuf)
                rowData.setField(9, null);  // sell_vol

                // Timing data
                rowData.setField(10, null);  // open_time
                rowData.setField(11, null);  // high_time
                rowData.setField(12, null);  // low_time
                rowData.setField(13, null);  // close_time

                // Additional fields
                rowData.setField(14, null);  // open_interest

                // Metadata fields
                rowData.setField(15, StringData.fromString(requestId));
                rowData.setField(16, ingestedAt);

                out.collect(rowData);
            }

            LOG.debug("Converted batch to {} RowData records: request_id={}",
                    batch.getRowCount(), requestId);
        }
    }
}
