package com.dexorder.flink.publisher;

import com.dexorder.proto.OHLC;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;

/**
 * Flink sink that publishes completed realtime OHLC bars to clients.
 *
 * Connects a ZMQ PUSH socket to the job manager's notification PULL endpoint.
 * The HistoryNotificationForwarder (already running on the job manager) receives these
 * frames and enqueues them to RealtimeSubscriptionManager, which publishes them on
 * the MARKET_DATA_PUB XPUB socket. Clients subscribed to the matching topic receive the bar.
 *
 * Wire format (matches HistoryNotificationPublisher):
 *   Frame 1: topic bytes  (e.g., "BTC/USDT.BINANCE|ohlc:60")
 *   Frame 2: [0x01]       (protocol version)
 *   Frame 3: [0x04][OHLC protobuf bytes]  (type 0x04 = OHLC single bar)
 *
 * Parallelism MUST be 1 (same as the rest of the notification pipeline).
 */
public class RealtimeBarPublisher extends RichSinkFunction<RealtimeBar> {
    private static final Logger LOG = LoggerFactory.getLogger(RealtimeBarPublisher.class);
    private static final long serialVersionUID = 1L;

    private static final byte PROTOCOL_VERSION = 0x01;
    private static final byte MSG_TYPE_OHLC = 0x04;

    private final String jobManagerPullEndpoint;

    private transient ZContext context;
    private transient ZMQ.Socket pushSocket;

    public RealtimeBarPublisher(String jobManagerPullEndpoint) {
        this.jobManagerPullEndpoint = jobManagerPullEndpoint;
    }

    @Override
    public void open(Configuration parameters) {
        context = new ZContext();
        pushSocket = context.createSocket(SocketType.PUSH);
        pushSocket.setLinger(1000);
        pushSocket.setSndHWM(10000);
        pushSocket.connect(jobManagerPullEndpoint);
        LOG.info("RealtimeBarPublisher PUSH connected to {}", jobManagerPullEndpoint);
    }

    @Override
    public void invoke(RealtimeBar bar, Context context) {
        try {
            // Build OHLC proto — timestamp in nanoseconds (bar uses ms, convert)
            OHLC ohlc = OHLC.newBuilder()
                    .setTimestamp(bar.getWindowStartMs() * 1_000_000L) // ms → ns
                    .setTicker(bar.getTicker())
                    .setOpen(bar.getOpen())
                    .setHigh(bar.getHigh())
                    .setLow(bar.getLow())
                    .setClose(bar.getClose())
                    .setVolume(bar.getVolume())
                    .build();

            byte[] protoBytes = ohlc.toByteArray();
            byte[] messageFrame = new byte[1 + protoBytes.length];
            messageFrame[0] = MSG_TYPE_OHLC;
            System.arraycopy(protoBytes, 0, messageFrame, 1, protoBytes.length);

            String topic = bar.topic();
            pushSocket.sendMore(topic.getBytes(ZMQ.CHARSET));
            pushSocket.sendMore(new byte[]{PROTOCOL_VERSION});
            pushSocket.send(messageFrame, 0);

            LOG.debug("Published realtime bar: topic={}, ticks={}", topic, bar.getTickCount());

        } catch (Exception e) {
            LOG.error("Failed to publish realtime bar: ticker={}, period={}",
                    bar.getTicker(), bar.getPeriodSeconds(), e);
        }
    }

    @Override
    public void close() {
        if (pushSocket != null) pushSocket.close();
        if (context != null) context.close();
        LOG.info("RealtimeBarPublisher closed");
    }
}
