package com.zcloud.personnel.positioning.integration.kafka;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.zcloud.personnel.positioning.command.query.RealtimeLocationPayloadException;
import com.zcloud.personnel.positioning.command.query.RealtimePersonLocationMatcher;
import com.zcloud.personnel.positioning.dto.clientobject.BiPersonLocationCO;
import com.zcloud.personnel.positioning.integration.realtime.RealtimeLocationPublisher;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.header.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

@Component
@ConditionalOnProperty(
        prefix = "personnel-positioning.kafka.location",
        name = "enabled",
        havingValue = "true"
)
public class LocationKafkaProbeListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(LocationKafkaProbeListener.class);

    private final int maxLogBytes;
    private final RealtimePersonLocationMatcher locationMatcher;
    private final ObjectMapper objectMapper;
    private final ObjectProvider<RealtimeLocationPublisher> publishers;

    public LocationKafkaProbeListener(
            @Value("${personnel-positioning.kafka.location.max-log-bytes:16384}") int maxLogBytes,
            RealtimePersonLocationMatcher locationMatcher,
            ObjectMapper objectMapper,
            ObjectProvider<RealtimeLocationPublisher> publishers) {
        this.maxLogBytes = Math.max(1, maxLogBytes);
        this.locationMatcher = locationMatcher;
        this.objectMapper = objectMapper;
        this.publishers = publishers;
    }

    @KafkaListener(
            id = "personnelPositionLocationProbeListener",
            topics = "${personnel-positioning.kafka.location.topic:point_push}",
            groupId = "${personnel-positioning.kafka.location.group-id:personnel-position-location-probe}"
    )
    public void onMessage(ConsumerRecord<byte[], byte[]> record, Acknowledgment acknowledgment) {
        LOGGER.info(
                "收到Kafka定位原始消息，topic={}, partition={}, offset={}, timestamp={}, "
                        + "timestampType={}, keyBytes={}, valueBytes={}, key={}, headers={}, value={}",
                record.topic(),
                record.partition(),
                record.offset(),
                record.timestamp(),
                record.timestampType(),
                length(record.key()),
                length(record.value()),
                LocationKafkaPayloadPreview.render(record.key(), maxLogBytes),
                renderHeaders(record),
                LocationKafkaPayloadPreview.render(record.value(), maxLogBytes)
        );

        try {
            List<BiPersonLocationCO> locations = locationMatcher.match(record.value());
            int deliveries = 0;
            for (BiPersonLocationCO location : locations) {
                String payload = serialize(location);
                for (RealtimeLocationPublisher publisher : publishers) {
                    deliveries += publisher.publish(payload);
                }
            }
            acknowledgment.acknowledge();
            LOGGER.info("Kafka location message processed, topic={}, partition={}, offset={}, converted={}, "
                            + "websocketDeliveries={}",
                    record.topic(), record.partition(), record.offset(), locations.size(), deliveries);
        } catch (RealtimeLocationPayloadException e) {
            acknowledgment.acknowledge();
            LOGGER.warn("Ignored invalid Kafka location message, topic={}, partition={}, offset={}, reason={}",
                    record.topic(), record.partition(), record.offset(), e.getMessage());
        }
    }

    private String serialize(BiPersonLocationCO location) {
        try {
            ObjectNode payload = objectMapper.valueToTree(location);
            List<String> nullFields = new ArrayList<>();
            Iterator<Map.Entry<String, JsonNode>> fields = payload.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                if (field.getValue().isNull()) {
                    nullFields.add(field.getKey());
                }
            }
            payload.remove(nullFields);
            return objectMapper.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Failed to serialize FindS personnel location", e);
        }
    }

    private int length(byte[] value) {
        return value == null ? 0 : value.length;
    }

    private String renderHeaders(ConsumerRecord<byte[], byte[]> record) {
        StringJoiner joiner = new StringJoiner(", ", "[", "]");
        for (Header header : record.headers()) {
            joiner.add(header.key() + "=" + LocationKafkaPayloadPreview.render(header.value(), maxLogBytes));
        }
        return joiner.toString();
    }
}
