package com.lingniu.ingest.protocol.jt808.downlink;

import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder;
import com.lingniu.ingest.protocol.jt808.model.Jt808Message;
import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry;
import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests;
import com.lingniu.ingest.session.CommandDispatcher;
import com.lingniu.ingest.session.DeviceSession;
import com.lingniu.ingest.session.SessionStore;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * JT808 侧的 {@link CommandDispatcher} 实现：把 session-core 的调用桥接到具体 Netty Channel。
 *
 * <p>{@link #notify(String, Object)} 异步下发后立即返回；
 * {@link #request(String, Object, Class, Duration)} 通过 {@link Jt808PendingRequests} 等待设备应答。
 *
 * <p>入参 {@code command} 必须是 {@link Jt808Commands.DownlinkCommand}，其他类型返回失败 future。
 */
public final class Jt808CommandDispatcher implements CommandDispatcher {

    private static final Logger log = LoggerFactory.getLogger(Jt808CommandDispatcher.class);

    private final SessionStore sessions;
    private final Jt808ChannelRegistry channelRegistry;
    private final Jt808PendingRequests pendingRequests;

    public Jt808CommandDispatcher(SessionStore sessions,
                                  Jt808ChannelRegistry channelRegistry,
                                  Jt808PendingRequests pendingRequests) {
        this.sessions = sessions;
        this.channelRegistry = channelRegistry;
        this.pendingRequests = pendingRequests;
    }

    @Override
    public CompletableFuture<Void> notify(String sessionId, Object command) {
        CommandContext ctx;
        try {
            ctx = resolve(sessionId, command);
        } catch (IllegalArgumentException e) {
            return CompletableFuture.failedFuture(e);
        }
        return writeFrames(ctx, false);
    }

    @Override
    public <T> CompletableFuture<T> request(String sessionId, Object command,
                                            Class<T> responseType, Duration timeout) {
        CommandContext ctx;
        try {
            ctx = resolve(sessionId, command);
        } catch (IllegalArgumentException e) {
            return CompletableFuture.failedFuture(e);
        }

        CompletableFuture<Jt808Message> pending = pendingRequests.await(ctx.phone, ctx.serial);
        writeFrames(ctx, true).whenComplete((unused, ex) -> {
            if (ex != null) {
                // 写 channel 失败时必须移除 pending，否则后续同 phone/serial 重用会匹配到旧 future。
                pendingRequests.cancel(ctx.phone, ctx.serial);
                pending.completeExceptionally(ex);
            }
        });

        return pending.orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS)
                .whenComplete((r, ex) -> {
                    // 超时/取消同样清理 pending，避免长期运行后内存泄漏。
                    if (ex != null) pendingRequests.cancel(ctx.phone, ctx.serial);
                })
                .thenApply(msg -> {
                    if (responseType.isInstance(msg)) return responseType.cast(msg);
                    throw new CompletionException(new ClassCastException(
                            "jt808 response type mismatch, expect=" + responseType.getSimpleName()
                                    + " actual=" + msg.getClass().getSimpleName()));
                });
    }

    private CompletableFuture<Void> writeFrames(CommandContext ctx, boolean request) {
        var frames = Jt808FrameEncoder.encodeSubpackages(
                ctx.cmd.messageId(), ctx.phone, ctx.serial, ctx.cmd.body());
        CompletableFuture<Void> cf = new CompletableFuture<>();
        AtomicInteger remaining = new AtomicInteger(frames.size());
        for (byte[] frame : frames) {
            ctx.channel.writeAndFlush(Unpooled.wrappedBuffer(frame)).addListener(f -> {
                if (!f.isSuccess()) {
                    if (request) {
                        pendingRequests.cancel(ctx.phone, ctx.serial);
                    }
                    cf.completeExceptionally(f.cause());
                    return;
                }
                if (remaining.decrementAndGet() == 0) {
                    cf.complete(null);
                }
            });
        }
        return cf;
    }

    private CommandContext resolve(String sessionId, Object command) {
        if (!(command instanceof Jt808Commands.DownlinkCommand cmd)) {
            throw new IllegalArgumentException(
                    "expected Jt808Commands.DownlinkCommand, got " + command.getClass().getName());
        }
        String phone = resolvePhone(sessionId);
        Channel channel = channelRegistry.find(phone)
                .orElseThrow(() -> new IllegalStateException("no active channel for phone=" + phone));
        int serial = channelRegistry.nextSerial(phone);
        return new CommandContext(phone, serial, channel, cmd);
    }

    private String resolvePhone(String sessionId) {
        Optional<DeviceSession> s = sessions.findBySessionId(sessionId)
                .or(() -> sessions.findByPhone(sessionId))
                .or(() -> sessions.findByVin(sessionId));
        // HTTP 调用方可以传 sessionId/VIN/phone；最终写 Netty Channel 必须落到 JT808 phone。
        return s.map(DeviceSession::phone)
                .filter(p -> p != null && !p.isBlank())
                .orElse(sessionId); // 回落：直接把 sessionId 当 phone
    }

    private record CommandContext(String phone, int serial, Channel channel, Jt808Commands.DownlinkCommand cmd) {}
}
