package com.seer.teach.netty.im.manager;

import com.seer.teach.netty.common.dto.Pending;
import com.seer.teach.netty.common.proto.MessageProtobuf;
import io.netty.channel.Channel;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Promise;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Slf4j
public final class AckSender {

    private final EventExecutor executor;
    private final int maxRetry;
    private final long timeoutMillis;
    private final long retryIntervalMillis;

    public AckSender(EventExecutor executor, int maxRetry, long timeoutMillis, long retryIntervalMillis) {
        this.executor = executor;
        this.maxRetry = maxRetry;
        this.timeoutMillis = timeoutMillis;
        this.retryIntervalMillis = retryIntervalMillis;
    }

    public Promise<Boolean> sendWithAck(String device, MessageProtobuf.Msg msg) {
        final Promise<Boolean> promise = new DefaultPromise<>(executor);
        final String msgId = msg.getHead().getMsgId();

        Pending pending = new Pending(msgId, null, promise);
        PendingAckManager.put(pending);

        sendOrRetry(device, pending, msg);
        return promise;
    }

    private void sendOrRetry(String device, Pending pending, MessageProtobuf.Msg msg) {
        Channel channel = ImChannelManager.get(device);

        if (channel == null || !channel.isActive()) {
            // 设备离线，等待重试
            log.info("设备离线或通道不可用，等待重试 device={}", device);
            executor.schedule(() -> sendOrRetry(device, pending, msg), retryIntervalMillis, TimeUnit.MILLISECONDS);
            return;
        }

        // 更新 channel
        pending.channel = channel;

        // 增加尝试次数
        int attempt = pending.attempts.incrementAndGet();

        if (attempt > maxRetry) {
            PendingAckManager.remove(pending.msgId);
            promiseFailOnce(pending, new TimeoutException("达到最大重试次数 msgId=" + pending.msgId));
            return;
        }

        // 写消息
        channel.writeAndFlush(msg).addListener(f -> {
            if (!f.isSuccess()) {
                log.warn("发送失败 msgId={}, device={}, attempt={}", pending.msgId, channel.id(), attempt);
                executor.schedule(() -> sendOrRetry(device, pending, msg), retryIntervalMillis, TimeUnit.MILLISECONDS);
            } else {
                // 写成功，启动超时检测
                if (pending.timeoutTask != null && !pending.timeoutTask.isDone()) {
                    pending.timeoutTask.cancel(false);
                }
                pending.timeoutTask = executor.schedule(() -> {
                    log.info("ACK 超时 进入重发 msgId={}, attempt={}", pending.msgId, attempt);
                    sendOrRetry(device, pending, msg); // 重发
                }, timeoutMillis, TimeUnit.MILLISECONDS);
            }
        });
    }

    private void promiseFailOnce(Pending pending, Throwable cause) {
        if (!pending.promise.isDone()) {
            pending.promise.tryFailure(cause);
        }
    }
}
