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

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.Nullable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.SingleThreadEventExecutor;
import org.springframework.data.redis.connection.RedisConnection;

import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.*;

@Slf4j
public final class DebugNetworkProbe {

    /** 通过 JVM 启动参数控制: -Dim.debug.probe=true */
    private static final boolean ENABLED = true;

    /** 打点节流间隔（毫秒）——避免刷屏 */
    private static final long LOG_THROTTLE_MS = Long.getLong("im.debug.probe.throttleMs", 2000L);

    /** Redis 探测超时（毫秒） */
    private static final long REDIS_TIMEOUT_MS = Long.getLong("im.debug.probe.redisTimeoutMs", 80L);

    /** 可选外部探测开关与地址（默认关闭）：
     *  -Dim.debug.probe.httpUrl=http://127.0.0.1:8080/healthz
     *  -Dim.debug.probe.http=true
     */
    private static final boolean HTTP_ENABLED = Boolean.getBoolean("im.debug.probe.http");
    private static final String HTTP_URL = System.getProperty("im.debug.probe.httpUrl", "");

    /** 单线程池：避免在 I/O 线程上阻塞 */
    private static final ExecutorService EXEC = new ThreadPoolExecutor(
            1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
            r -> {
                Thread t = new Thread(r, "im-debug-probe");
                t.setDaemon(true);
                return t;
            });

    private static volatile long lastLogTs = 0;

    private DebugNetworkProbe() {}

    public static void probe(ChannelHandlerContext ctx,
                             @Nullable RedisTemplate<String, String> redisTemplate,
                             @Nullable String deviceHint) {
        if (!ENABLED) return;

        long now = System.currentTimeMillis();
        if (now - lastLogTs < LOG_THROTTLE_MS) return;
        lastLogTs = now;

        // 1) Netty 侧：可写性和“距不可写的剩余字节数”
        boolean writable = ctx.channel().isWritable();
        long bytesBeforeUnwritable = ctx.channel().bytesBeforeUnwritable();

        // 2) EventLoop 队列积压（若可获取）
        Integer pendingTasks = null;
        try {
            EventExecutor exec = ctx.executor();
            if (exec instanceof SingleThreadEventExecutor) {
                pendingTasks = ((SingleThreadEventExecutor) exec).pendingTasks();
            }
        } catch (Throwable ignore) {}

        // 3) Redis PING（在小线程里限时执行）
        String redisStatus = "SKIP";
        Long redisRttMs = null;
        if (redisTemplate != null) {
            long startNs = System.nanoTime();
            Future<String> f = EXEC.submit(() -> {
                try (RedisConnection c = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection()) {
                    String pong = c.ping();
                    return pong == null ? "NULL" : pong;
                } catch (Throwable t) {
                    return "ERR:" + t.getClass().getSimpleName();
                }
            });
            try {
                String ret = f.get(REDIS_TIMEOUT_MS, TimeUnit.MILLISECONDS);
                redisRttMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
                redisStatus = ret;
            } catch (TimeoutException te) {
                f.cancel(true);
                redisStatus = "TIMEOUT";
                redisRttMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            } catch (Exception e) {
                redisStatus = "ERR:" + e.getClass().getSimpleName();
                redisRttMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            }
        }

        // 4) 可选 HTTP 探测（同样限时）
        String httpStatus = "DISABLED";
        Long httpRttMs = null;
        if (HTTP_ENABLED && !HTTP_URL.isEmpty()) {
            long startNs = System.nanoTime();
            Future<Integer> f = EXEC.submit(() -> {
                try {
                    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) new java.net.URL(HTTP_URL).openConnection();
                    conn.setConnectTimeout(60);
                    conn.setReadTimeout(60);
                    conn.setRequestMethod("GET");
                    return conn.getResponseCode();
                } catch (Throwable t) {
                    return -1;
                }
            });
            try {
                int code = f.get(100, TimeUnit.MILLISECONDS);
                httpStatus = "HTTP " + code;
                httpRttMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            } catch (Exception e) {
                f.cancel(true);
                httpStatus = "HTTP ERR";
                httpRttMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            }
        }

        // 5) 计算一个非常粗糙的“等级”，仅用于调试日志
        String grade = grade(writable, bytesBeforeUnwritable, pendingTasks, redisStatus, redisRttMs);

        log.warn("[IM-DEBUG] ts={} device={} grade={} | writable={} bytesBeforeUnwr={} pendingTasks={} | redis={} ({}ms) | http={} ({}ms)",
                Instant.ofEpochMilli(now),
                deviceHint,
                grade,
                writable,
                bytesBeforeUnwritable,
                pendingTasks,
                redisStatus, redisRttMs,
                httpStatus, httpRttMs);
    }

    private static String grade(boolean writable, long bytesBeforeUnwritable,
                                Integer pendingTasks, String redisStatus, Long redisRttMs) {
        int score = 100;

        if (!writable) score -= 35;
        if (bytesBeforeUnwritable >= 0 && bytesBeforeUnwritable < 64 * 1024) score -= 15;
        if (pendingTasks != null) {
            if (pendingTasks > 500) score -= 30;
            else if (pendingTasks > 100) score -= 15;
        }
        if (redisStatus.startsWith("ERR") || "TIMEOUT".equals(redisStatus) || "NULL".equals(redisStatus)) {
            score -= 30;
        } else if ("PONG".equalsIgnoreCase(redisStatus) && redisRttMs != null) {
            if (redisRttMs > 50) score -= 10;
        }

        if (score >= 80) return "GOOD";
        if (score >= 60) return "FAIR";
        if (score >= 40) return "POOR";
        return "BAD";
    }
}
