package tech.easyflow.ai.easyagentsflow.event;

import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.easyflow.common.mq.core.MQDeadLetterService;
import tech.easyflow.common.mq.core.MQMessage;
import tech.easyflow.common.mq.core.MQProducer;

import javax.annotation.PreDestroy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 工作流执行审计事件生产者。
 *
 * <p>少量固定 lane 保证同一实例 FIFO，跨 lane 并行发送和重试，避免单个异常
 * 实例阻塞全部工作流。全局条数与字节预算共同约束本地重试内存。</p>
 */
@Service
public class WorkflowExecutionAuditProducer {

    private static final Logger log =
            LoggerFactory.getLogger(WorkflowExecutionAuditProducer.class);
    private static final int DEFAULT_LANE_COUNT = 8;
    private static final int MAX_LOCAL_BACKLOG = 10_000;
    private static final long MAX_MESSAGE_BYTES =
            64L * 1024L * 1024L;
    private static final long MAX_BACKLOG_BYTES =
            512L * 1024L * 1024L;
    private static final long SHUTDOWN_FLUSH_MILLIS =
            TimeUnit.SECONDS.toMillis(5L);
    private static final int MAX_DRAIN_BATCH = 256;
    private static final int MAX_SEND_ATTEMPTS = 16;
    private static final long MAX_RETRY_DELAY_MILLIS =
            TimeUnit.MINUTES.toMillis(1);

    private final MQProducer mqProducer;
    private final MQDeadLetterService deadLetterService;
    private final List<DeliveryLane> lanes;
    private final Object admissionLock =
            new Object();
    private final ScheduledExecutorService retryExecutor;
    private final AtomicBoolean closed =
            new AtomicBoolean();
    private final int maxLocalBacklog;
    private final long maxMessageBytes;
    private final long maxBacklogBytes;
    private final long shutdownFlushMillis;
    private int backlogCount;
    private long backlogBytes;

    /**
     * 创建工作流执行审计事件生产者。
     *
     * @param mqProducer 通用 MQ 生产者
     * @param deadLetterService 通用 MQ 死信服务
     */
    @Autowired
    public WorkflowExecutionAuditProducer(
            MQProducer mqProducer,
            MQDeadLetterService deadLetterService) {
        this(
                mqProducer,
                deadLetterService,
                DEFAULT_LANE_COUNT,
                MAX_LOCAL_BACKLOG,
                MAX_MESSAGE_BYTES,
                MAX_BACKLOG_BYTES,
                SHUTDOWN_FLUSH_MILLIS);
    }

    /**
     * 创建带测试预算的工作流审计生产者。
     *
     * @param mqProducer 通用 MQ 生产者
     * @param deadLetterService 通用 MQ 死信服务
     * @param laneCount 固定发送 lane 数
     * @param maxLocalBacklog 最大本地积压条数
     * @param maxMessageBytes 单条消息最大字节数
     * @param maxBacklogBytes 本地积压最大总字节数
     * @param shutdownFlushMillis 关闭时最大收口时间
     */
    WorkflowExecutionAuditProducer(
            MQProducer mqProducer,
            MQDeadLetterService deadLetterService,
            int laneCount,
            int maxLocalBacklog,
            long maxMessageBytes,
            long maxBacklogBytes,
            long shutdownFlushMillis) {
        this.mqProducer = mqProducer;
        this.deadLetterService = deadLetterService;
        this.maxLocalBacklog =
                Math.max(1, maxLocalBacklog);
        this.maxMessageBytes =
                Math.max(1L, maxMessageBytes);
        this.maxBacklogBytes =
                Math.max(this.maxMessageBytes,
                        maxBacklogBytes);
        this.shutdownFlushMillis =
                Math.max(0L, shutdownFlushMillis);
        int effectiveLaneCount =
                Math.max(1, laneCount);
        this.lanes =
                new ArrayList<>(effectiveLaneCount);
        for (int index = 0;
             index < effectiveLaneCount;
             index++) {
            lanes.add(new DeliveryLane());
        }
        this.retryExecutor =
                Executors.newScheduledThreadPool(
                        Math.min(
                                effectiveLaneCount, 4),
                        runnable -> {
                            Thread thread =
                                    new Thread(
                                            runnable,
                                            "workflow-audit-producer-retry");
                            thread.setDaemon(true);
                            return thread;
                        });
        for (DeliveryLane lane : lanes) {
            retryExecutor.scheduleWithFixedDelay(
                    () -> drainReadyBatch(
                            lane, false),
                    10L,
                    10L,
                    TimeUnit.MILLISECONDS);
        }
    }

    /**
     * 投递工作流执行审计事件。
     *
     * <p>同一实例稳定落在同一发送 lane 和 MQ 分片并保持事件顺序；不同 lane
     * 独立发送和退避。</p>
     *
     * @param event 工作流执行审计事件
     * @return Redis Stream 记录 ID，或本地排队标识
     */
    public String send(
            WorkflowExecutionAuditEvent event) {
        if (closed.get()) {
            throw new IllegalStateException(
                    "Workflow audit producer is closed");
        }
        if (event == null
                || event.getType() == null) {
            throw new IllegalArgumentException(
                    "Workflow execution audit event is required");
        }
        Date occurredAt =
                event.getOccurredAt() == null
                        ? new Date()
                        : event.getOccurredAt();
        event.setOccurredAt(occurredAt);

        MQMessage message =
                new MQMessage();
        message.setMessageId(event.getEventId());
        message.setTopic(
                WorkflowExecutionAuditMqConstants.TOPIC);
        message.setKey(event.getInstanceId());
        message.setCreatedAt(occurredAt);
        message.setBody(
                JSON.toJSONString(event));
        long messageBytes =
                messageBytes(message);
        ensureMessageSize(message, messageBytes);

        DeliveryLane lane =
                laneFor(message.getKey());
        synchronized (lane.lock) {
            if (closed.get()) {
                throw new IllegalStateException(
                        "Workflow audit producer is closed");
            }
            if (lane.sending
                    || !lane.backlog.isEmpty()) {
                enqueueLast(
                        lane,
                        new PendingDelivery(
                                message,
                                0,
                                0L,
                                messageBytes));
                return queuedId(message);
            }
            lane.sending = true;
        }
        try {
            return mqProducer.send(message);
        } catch (RuntimeException sendError) {
            boolean closing;
            synchronized (lane.lock) {
                closing = closed.get();
                if (!closing) {
                    enqueueFirst(
                            lane,
                            new PendingDelivery(
                                    message,
                                    1,
                                    System.currentTimeMillis()
                                            + retryDelayMillis(1),
                                    messageBytes));
                }
            }
            if (closing) {
                try {
                    deadLetterService.deadLetter(
                            message,
                            "producer closed during send failure");
                } catch (RuntimeException deadLetterError) {
                    sendError.addSuppressed(
                            deadLetterError);
                }
                throw new IllegalStateException(
                        "Workflow audit producer closed during send",
                        sendError);
            }
            log.warn(
                    "工作流审计 MQ 暂时不可用，事件已进入有界重试队列，eventId={}",
                    message.getMessageId());
            return queuedId(message);
        } finally {
            synchronized (lane.lock) {
                lane.sending = false;
            }
        }
    }

    /**
     * 在单个 lane 中按原顺序批量重试。
     *
     * @param lane 发送 lane
     * @param ignoreRetryTime 关闭收口时是否忽略退避时间
     */
    private void drainReadyBatch(
            DeliveryLane lane,
            boolean ignoreRetryTime) {
        for (int index = 0;
             index < MAX_DRAIN_BATCH;
             index++) {
            if (!drainOne(
                    lane, ignoreRetryTime)) {
                return;
            }
        }
    }

    /**
     * 重试一个 lane 的队首消息。
     *
     * @param lane 发送 lane
     * @param ignoreRetryTime 是否忽略退避时间
     * @return 队首已移除且可继续排空时为 {@code true}
     */
    private boolean drainOne(
            DeliveryLane lane,
            boolean ignoreRetryTime) {
        PendingDelivery pending;
        synchronized (lane.lock) {
            if (lane.sending
                    || lane.backlog.isEmpty()) {
                return false;
            }
            pending = lane.backlog.peekFirst();
            if (pending == null
                    || (!ignoreRetryTime
                    && pending.nextAttemptAtMillis()
                    > System.currentTimeMillis())) {
                return false;
            }
            lane.sending = true;
        }
        boolean removed = false;
        try {
            mqProducer.send(pending.message());
            removed = removeHead(
                    lane, pending);
        } catch (RuntimeException sendError) {
            int nextAttempt =
                    pending.attempt() + 1;
            if (nextAttempt
                    >= MAX_SEND_ATTEMPTS) {
                if (deadLetter(
                        pending.message(),
                        sendError)) {
                    removed = removeHead(
                            lane, pending);
                } else {
                    replaceHead(
                            lane,
                            pending,
                            pending.retryAt(
                                    nextAttempt,
                                    System.currentTimeMillis()
                                            + MAX_RETRY_DELAY_MILLIS));
                }
            } else {
                replaceHead(
                        lane,
                        pending,
                        pending.retryAt(
                                nextAttempt,
                                System.currentTimeMillis()
                                        + retryDelayMillis(
                                        nextAttempt)));
            }
        } finally {
            synchronized (lane.lock) {
                lane.sending = false;
            }
        }
        return removed;
    }

    /**
     * 将耗尽生产重试的事件写入通用死信流。
     *
     * @param message MQ 消息
     * @param failure 最终发送异常
     * @return 死信写入成功时为 {@code true}
     */
    private boolean deadLetter(
            MQMessage message,
            RuntimeException failure) {
        try {
            deadLetterService.deadLetter(
                    message,
                    "producer send attempts exhausted: "
                            + failure.getClass().getName()
                            + ": "
                            + failure.getMessage());
            return true;
        } catch (RuntimeException deadLetterError) {
            log.error(
                    "工作流审计生产失败且死信写入失败，eventId={}",
                    message.getMessageId(),
                    deadLetterError);
            return false;
        }
    }

    /**
     * 删除仍位于 lane 队首的消息并归还全局积压预算。
     *
     * @param lane 发送 lane
     * @param expected 期望队首
     * @return 成功删除时为 {@code true}
     */
    private boolean removeHead(
            DeliveryLane lane,
            PendingDelivery expected) {
        synchronized (lane.lock) {
            if (lane.backlog.peekFirst()
                    != expected) {
                return false;
            }
            lane.backlog.removeFirst();
            releaseAdmission(expected);
            return true;
        }
    }

    /**
     * 原子替换仍位于 lane 队首的消息。
     *
     * @param lane 发送 lane
     * @param expected 当前队首
     * @param replacement 替换项
     */
    private void replaceHead(
            DeliveryLane lane,
            PendingDelivery expected,
            PendingDelivery replacement) {
        synchronized (lane.lock) {
            if (lane.backlog.peekFirst()
                    == expected) {
                lane.backlog.removeFirst();
                lane.backlog.addFirst(
                        replacement);
            }
        }
    }

    /**
     * 入队到指定 lane 尾部。
     *
     * @param lane 发送 lane
     * @param pending 待投递事件
     */
    private void enqueueLast(
            DeliveryLane lane,
            PendingDelivery pending) {
        reserveAdmission(pending);
        lane.backlog.addLast(pending);
    }

    /**
     * 入队到指定 lane 头部。
     *
     * @param lane 发送 lane
     * @param pending 待投递事件
     */
    private void enqueueFirst(
            DeliveryLane lane,
            PendingDelivery pending) {
        reserveAdmission(pending);
        lane.backlog.addFirst(pending);
    }

    /**
     * 预占全局积压条数和字节预算。
     *
     * @param pending 待入队消息
     */
    private void reserveAdmission(
            PendingDelivery pending) {
        boolean rejected;
        synchronized (admissionLock) {
            rejected = backlogCount
                    >= maxLocalBacklog
                    || pending.messageBytes()
                    > maxBacklogBytes
                    - backlogBytes;
            if (!rejected) {
                backlogCount++;
                backlogBytes +=
                        pending.messageBytes();
            }
        }
        if (rejected) {
            deadLetterService.deadLetter(
                    pending.message(),
                    "producer retry queue is full");
            throw new IllegalStateException(
                    "Workflow audit producer retry queue is full");
        }
    }

    /**
     * 归还一条积压消息占用的全局预算。
     *
     * @param pending 已移除消息
     */
    private void releaseAdmission(
            PendingDelivery pending) {
        synchronized (admissionLock) {
            backlogCount =
                    Math.max(0, backlogCount - 1);
            backlogBytes =
                    Math.max(
                            0L,
                            backlogBytes
                                    - pending.messageBytes());
        }
    }

    /**
     * 校验单条消息字节上限。
     *
     * @param message MQ 消息
     * @param bytes 消息估算字节数
     */
    private void ensureMessageSize(
            MQMessage message,
            long bytes) {
        if (bytes <= maxMessageBytes) {
            return;
        }
        deadLetterService.deadLetter(
                message,
                "producer message exceeds byte limit");
        throw new IllegalArgumentException(
                "Workflow audit message exceeds byte limit");
    }

    /**
     * 估算 MQ 消息本地持有字节数。
     *
     * @param message MQ 消息
     * @return UTF-8 负载和关键元数据字节数
     */
    private long messageBytes(
            MQMessage message) {
        return utf8Bytes(message.getBody())
                + utf8Bytes(message.getMessageId())
                + utf8Bytes(message.getKey())
                + 128L;
    }

    /**
     * 计算字符串 UTF-8 字节数。
     *
     * @param value 字符串
     * @return 字节数
     */
    private long utf8Bytes(String value) {
        if (value == null) {
            return 0L;
        }
        long bytes = 0L;
        for (int index = 0;
             index < value.length();
             index++) {
            char current =
                    value.charAt(index);
            if (current <= 0x7F) {
                bytes++;
            } else if (current <= 0x7FF) {
                bytes += 2L;
            } else if (Character.isHighSurrogate(
                    current)
                    && index + 1 < value.length()
                    && Character.isLowSurrogate(
                    value.charAt(index + 1))) {
                bytes += 4L;
                index++;
            } else {
                bytes += 3L;
            }
        }
        return bytes;
    }

    /**
     * 按稳定 key 选择固定发送 lane。
     *
     * @param key 工作流实例键
     * @return 发送 lane
     */
    private DeliveryLane laneFor(String key) {
        int hash = key == null
                ? 0
                : key.hashCode();
        return lanes.get(
                Math.floorMod(hash, lanes.size()));
    }

    /**
     * 构造本地排队返回标识。
     *
     * @param message 已排队消息
     * @return 排队标识
     */
    private String queuedId(
            MQMessage message) {
        return "queued:"
                + message.getMessageId();
    }

    /**
     * 计算生产者重试退避。
     *
     * @param attempt 已失败次数
     * @return 退避毫秒数
     */
    private long retryDelayMillis(int attempt) {
        int shift =
                Math.min(
                        16,
                        Math.max(0, attempt - 1));
        return Math.min(
                MAX_RETRY_DELAY_MILLIS,
                100L << shift);
    }

    /**
     * 关闭生产者重试线程，并在有界时间内发送或死信收口积压事件。
     */
    @PreDestroy
    public void close() {
        if (!closed.compareAndSet(
                false, true)) {
            return;
        }
        retryExecutor.shutdownNow();
        long deadline =
                System.nanoTime()
                        + TimeUnit.MILLISECONDS.toNanos(
                        shutdownFlushMillis);
        while (hasBacklog()
                && System.nanoTime() < deadline) {
            boolean progressed = false;
            for (DeliveryLane lane : lanes) {
                int before = laneSize(lane);
                drainReadyBatch(lane, true);
                progressed |= laneSize(lane)
                        < before;
            }
            if (!progressed) {
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException error) {
                    Thread.currentThread()
                            .interrupt();
                    break;
                }
            }
        }
        deadLetterRemainingBacklog();
    }

    /**
     * 判断是否仍有本地积压消息。
     *
     * @return 有积压时为 {@code true}
     */
    private boolean hasBacklog() {
        synchronized (admissionLock) {
            return backlogCount > 0;
        }
    }

    /**
     * 获取 lane 当前积压条数。
     *
     * @param lane 发送 lane
     * @return 积压条数
     */
    private int laneSize(DeliveryLane lane) {
        synchronized (lane.lock) {
            return lane.backlog.size();
        }
    }

    /**
     * 将关闭期限后剩余消息转入死信，避免进程内静默丢失。
     */
    private void deadLetterRemainingBacklog() {
        for (DeliveryLane lane : lanes) {
            List<PendingDelivery> remaining =
                    new ArrayList<>();
            synchronized (lane.lock) {
                while (!lane.backlog.isEmpty()) {
                    PendingDelivery pending =
                            lane.backlog.removeFirst();
                    remaining.add(pending);
                    releaseAdmission(pending);
                }
            }
            for (PendingDelivery pending :
                    remaining) {
                try {
                    deadLetterService.deadLetter(
                            pending.message(),
                            "producer shutdown flush timeout");
                } catch (RuntimeException error) {
                    log.error(
                            "工作流审计关闭收口死信失败，eventId={}",
                            pending.message()
                                    .getMessageId(),
                            error);
                }
            }
        }
    }

    /**
     * 一条有序待投递审计消息。
     *
     * @param message MQ 消息
     * @param attempt 已失败次数
     * @param nextAttemptAtMillis 下次允许重试时间
     * @param messageBytes 本地持有字节数
     */
    private record PendingDelivery(
            MQMessage message,
            int attempt,
            long nextAttemptAtMillis,
            long messageBytes) {

        /**
         * 创建下一次重试记录。
         *
         * @param nextAttempt 下一次尝试次数
         * @param retryAt 下次允许发送时间
         * @return 保留原消息与字节大小的新记录
         */
        private PendingDelivery retryAt(
                int nextAttempt,
                long retryAt) {
            return new PendingDelivery(
                    message,
                    nextAttempt,
                    retryAt,
                    messageBytes);
        }
    }

    /**
     * 一个独立 FIFO 发送 lane。
     */
    private static final class DeliveryLane {
        private final Object lock =
                new Object();
        private final Deque<PendingDelivery> backlog =
                new ArrayDeque<>();
        private boolean sending;
    }
}
