package com.lingniu.ingest.api.consumer;

import java.time.Instant;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

public final class EnvelopeConsumerProcessor {

    private static final Set<EnvelopeIngestResult.Status> DEAD_LETTER_STATUSES = EnumSet.of(
            EnvelopeIngestResult.Status.SKIPPED,
            EnvelopeIngestResult.Status.INVALID_ENVELOPE,
            EnvelopeIngestResult.Status.FAILED
    );

    private final String service;
    private final EnvelopeIngestor ingestor;
    private final EnvelopeDeadLetterSink deadLetterSink;

    public EnvelopeConsumerProcessor(String service, EnvelopeIngestor ingestor, EnvelopeDeadLetterSink deadLetterSink) {
        if (ingestor == null) {
            throw new IllegalArgumentException("ingestor must not be null");
        }
        if (deadLetterSink == null) {
            throw new IllegalArgumentException("deadLetterSink must not be null");
        }
        this.service = service == null || service.isBlank() ? "unknown" : service;
        this.ingestor = ingestor;
        this.deadLetterSink = deadLetterSink;
    }

    public EnvelopeIngestResult process(EnvelopeConsumerRecord record) {
        if (record == null) {
            throw new IllegalArgumentException("record must not be null");
        }
        EnvelopeIngestResult result = processAll(List.of(record)).getFirst();
        return result;
    }

    public List<EnvelopeIngestResult> processAll(List<EnvelopeConsumerRecord> records) {
        if (records == null) {
            throw new IllegalArgumentException("records must not be null");
        }
        if (records.isEmpty()) {
            return List.of();
        }
        List<byte[]> payloads = new ArrayList<>(records.size());
        for (EnvelopeConsumerRecord record : records) {
            if (record == null) {
                throw new IllegalArgumentException("record must not be null");
            }
            payloads.add(record.payload());
        }
        List<EnvelopeIngestResult> results = ingestor.tryIngestAll(payloads);
        if (results.size() != records.size()) {
            throw new IllegalStateException("ingestor result count does not match record count");
        }
        for (int i = 0; i < records.size(); i++) {
            publishDeadLetterIfNeeded(records.get(i), results.get(i));
        }
        return results;
    }

    private void publishDeadLetterIfNeeded(EnvelopeConsumerRecord record, EnvelopeIngestResult result) {
        // 派生消费者不在这里抛出业务异常给 Kafka worker；坏消息统一进入 DLQ，
        // worker 看到 process 正常返回后才能提交 offset，避免同一坏消息无限阻塞消费组。
        if (DEAD_LETTER_STATUSES.contains(result.status())) {
            deadLetterSink.publish(toDeadLetter(record, result));
        }
    }

    public List<EnvelopeIngestResult> processBatch(List<EnvelopeConsumerRecord> records) {
        if (records == null || records.isEmpty()) {
            return List.of();
        }
        if (!(ingestor instanceof EnvelopeBatchIngestor batchIngestor)) {
            return records.stream().map(this::process).toList();
        }
        List<byte[]> payloads = records.stream()
                .map(EnvelopeConsumerRecord::payload)
                .toList();
        List<EnvelopeIngestResult> results = batchIngestor.tryIngestAll(payloads);
        if (results.size() != records.size()) {
            throw new IllegalStateException("batch ingestor returned " + results.size()
                    + " results for " + records.size() + " records");
        }
        List<EnvelopeIngestResult> copy = new ArrayList<>(results.size());
        for (int i = 0; i < records.size(); i++) {
            EnvelopeIngestResult result = results.get(i);
            if (DEAD_LETTER_STATUSES.contains(result.status())) {
                deadLetterSink.publish(toDeadLetter(records.get(i), result));
            }
            copy.add(result);
        }
        return List.copyOf(copy);
    }

    private EnvelopeDeadLetterRecord toDeadLetter(EnvelopeConsumerRecord record, EnvelopeIngestResult result) {
        return new EnvelopeDeadLetterRecord(
                service,
                record.topic(),
                record.partition(),
                record.offset(),
                record.key(),
                result.status(),
                result.eventId(),
                result.vin(),
                result.message(),
                record.payload(),
                Instant.now());
    }
}
