package com.goldenchart.trading.pipeline;

import com.goldenchart.storage.Ta4jStorage;
import com.goldenchart.websocket.UpbitWebSocketClient;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 멀티스레드 파이프라인 운영 모니터링 (대시보드·/trading/pipeline/status).
 */
@Service
@RequiredArgsConstructor
public class PipelineMonitorService {

    private static final String[] MONITORED_THREAD_PREFIXES = {
            "core-engine-worker",
            "order-execution",
            "upbit-ws-reconnect",
            "ws-staleness-monitor",
            "warmup-thread",
            "WebSocketConnectReadThread",
    };

    private final TradingPipelineProperties props;
    private final TradeEventQueue eventQueue;
    private final CoreEngineWorkerPool workerPool;
    private final OrderExecutionQueue orderQueue;
    private final TickActivityTracker activityTracker;
    private final UpbitWebSocketClient upbitWebSocketClient;
    private final WebSocketStalenessMonitor stalenessMonitor;
    private final CandleGapBackfillService gapBackfillService;
    private final Ta4jStorage ta4jStorage;

    private long prevSampleMs;
    private long prevEnqueued;
    private long prevDequeued;
    private long prevProcessed;
    private long prevWsMessages;
    private long prevOrdersExecuted;

    public Map<String, Object> buildMonitorSnapshot() {
        Map<String, Object> out = new LinkedHashMap<>();
        boolean enabled = props.isEnabled();
        out.put("pipelineEnabled", enabled);
        out.put("sampledAtMs", System.currentTimeMillis());
        out.put("config", Map.of(
                "workerThreads", props.getWorkerThreads(),
                "queueCapacity", props.getQueueCapacity(),
                "orderRatePerSecond", props.getOrderRatePerSecond(),
                "maxBarsPerSeries", props.getMaxBarsPerSeries(),
                "staleTickSeconds", props.getStaleTickSeconds(),
                "gapBackfillCron", props.getGapBackfillCron()
        ));
        out.put("jvm", jvmMetrics());
        out.put("traffic", computeTrafficRates());
        out.put("memory", ta4jStorage.storageMetrics());
        out.put("queue", eventQueue.stats());
        out.put("workers", workerPool.stats());
        out.put("orders", orderQueue.stats());
        out.put("lastTicks", activityTracker.snapshot());
        out.put("staleMarkets", findStaleMarkets());
        out.put("threads", listMonitoredThreads());
        out.put("processes", buildProcessList(enabled));
        return out;
    }

    private Map<String, Object> jvmMetrics() {
        MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
        long heapUsed = mem.getHeapMemoryUsage().getUsed();
        long heapMax = mem.getHeapMemoryUsage().getMax();
        long nonHeap = mem.getNonHeapMemoryUsage().getUsed();
        ThreadMXBean threads = ManagementFactory.getThreadMXBean();
        double heapPct = heapMax > 0 ? (heapUsed * 100.0 / heapMax) : 0;
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("heapUsedMb", roundMb(heapUsed));
        m.put("heapMaxMb", roundMb(heapMax));
        m.put("heapPct", Math.round(heapPct * 10) / 10.0);
        m.put("nonHeapUsedMb", roundMb(nonHeap));
        m.put("threadCount", threads.getThreadCount());
        m.put("daemonThreadCount", threads.getDaemonThreadCount());
        return m;
    }

    private Map<String, Object> computeTrafficRates() {
        long now = System.currentTimeMillis();
        var q = eventQueue.stats();
        var w = workerPool.stats();
        var o = orderQueue.stats();
        var ws = upbitWebSocketClient.monitorSnapshot();
        long wsMsg = ((Number) ws.get("messagesReceived")).longValue();

        Map<String, Object> rates = new LinkedHashMap<>();
        if (prevSampleMs > 0) {
            double sec = Math.max(0.5, (now - prevSampleMs) / 1000.0);
            rates.put("ticksEnqueuedPerSec", round2((q.enqueued() - prevEnqueued) / sec));
            rates.put("ticksDequeuedPerSec", round2((q.dequeued() - prevDequeued) / sec));
            rates.put("ticksProcessedPerSec", round2((w.processed() - prevProcessed) / sec));
            rates.put("wsMessagesPerSec", round2((wsMsg - prevWsMessages) / sec));
            rates.put("ordersExecutedPerSec", round2((o.executed() - prevOrdersExecuted) / sec));
        } else {
            rates.put("ticksEnqueuedPerSec", 0.0);
            rates.put("ticksDequeuedPerSec", 0.0);
            rates.put("ticksProcessedPerSec", 0.0);
            rates.put("wsMessagesPerSec", 0.0);
            rates.put("ordersExecutedPerSec", 0.0);
        }

        prevSampleMs = now;
        prevEnqueued = q.enqueued();
        prevDequeued = q.dequeued();
        prevProcessed = w.processed();
        prevWsMessages = wsMsg;
        prevOrdersExecuted = o.executed();
        return rates;
    }

    private List<Map<String, Object>> findStaleMarkets() {
        long now = System.currentTimeMillis();
        List<Map<String, Object>> stale = new ArrayList<>();
        for (String market : upbitWebSocketClient.getSubscribedMarkets()) {
            long last = activityTracker.lastTickMs(market);
            if (last == 0) continue;
            long ageSec = (now - last) / 1000;
            if (ageSec > props.getStaleTickSeconds()) {
                stale.add(Map.of("market", market, "lastTickAgeSec", ageSec));
            }
        }
        stale.sort((a, b) -> Long.compare(
                ((Number) b.get("lastTickAgeSec")).longValue(),
                ((Number) a.get("lastTickAgeSec")).longValue()));
        return stale;
    }

    private List<Map<String, Object>> listMonitoredThreads() {
        ThreadMXBean mx = ManagementFactory.getThreadMXBean();
        List<Map<String, Object>> list = new ArrayList<>();
        for (ThreadInfo info : mx.dumpAllThreads(false, false)) {
            if (info == null) continue;
            String name = info.getThreadName();
            boolean match = false;
            for (String prefix : MONITORED_THREAD_PREFIXES) {
                if (name.contains(prefix)) {
                    match = true;
                    break;
                }
            }
            if (!match) continue;
            list.add(Map.of(
                    "name", name,
                    "id", info.getThreadId(),
                    "state", info.getThreadState().name(),
                    "daemon", info.isDaemon()
            ));
        }
        return list;
    }

    private List<Map<String, Object>> buildProcessList(boolean pipelineEnabled) {
        List<Map<String, Object>> processes = new ArrayList<>();
        var ws = upbitWebSocketClient.monitorSnapshot();
        var q = eventQueue.stats();
        var w = workerPool.stats();
        var o = orderQueue.stats();

        processes.add(process("ingestion", "Upbit WebSocket (Ingestion)", "Ingestion",
                pipelineEnabled
                        ? (Boolean.TRUE.equals(ws.get("connected")) ? "healthy" : "down")
                        : (Boolean.TRUE.equals(ws.get("connected")) ? "healthy" : "down"),
                Map.of(
                        "connected", ws.get("connected"),
                        "subscribedMarkets", ws.get("subscribedMarkets"),
                        "messagesReceived", ws.get("messagesReceived"),
                        "parseErrors", ws.get("parseErrors"),
                        "reconnectDelaySec", ws.get("reconnectDelaySec")
                )));

        String queueStatus = !pipelineEnabled ? "disabled"
                : q.fillRatio() > 0.85 ? "degraded"
                : q.dropped() > 0 ? "degraded"
                : "healthy";
        processes.add(process("eventBuffer", "Trade Event Queue", "Event Buffer", queueStatus,
                Map.of(
                        "pending", q.pending(),
                        "capacity", q.capacity(),
                        "fillPct", Math.round(q.fillRatio() * 1000) / 10.0,
                        "enqueued", q.enqueued(),
                        "dequeued", q.dequeued(),
                        "dropped", q.dropped()
                )));

        String workerStatus = !pipelineEnabled ? "disabled"
                : !w.active() ? "down"
                : w.errors() > 0 ? "degraded"
                : "healthy";
        processes.add(process("coreEngine", "Core Engine Worker Pool", "Core Engine", workerStatus,
                Map.of(
                        "active", w.active(),
                        "workerThreads", w.workerThreads(),
                        "processed", w.processed(),
                        "errors", w.errors()
                )));

        String orderStatus = !pipelineEnabled ? "disabled"
                : !o.workerAlive() ? "down"
                : o.pending() > 100 ? "degraded"
                : "healthy";
        processes.add(process("orderExecution", "Order Execution", "Execution", orderStatus,
                Map.of(
                        "workerAlive", o.workerAlive(),
                        "pending", o.pending(),
                        "submitted", o.submitted(),
                        "executed", o.executed(),
                        "failed", o.failed(),
                        "dropped", o.dropped(),
                        "ratePerSecond", o.ratePerSecond()
                )));

        var stale = stalenessMonitor.monitorSnapshot();
        processes.add(process("stalenessMonitor", "WS Staleness Monitor", "Health",
                pipelineEnabled
                        ? (Boolean.TRUE.equals(stale.get("running")) ? "healthy" : "down")
                        : "disabled",
                stale));

        processes.add(process("gapBackfill", "Candle Gap Backfill", "Maintenance",
                pipelineEnabled ? "healthy" : "disabled",
                gapBackfillService.monitorSnapshot()));

        processes.add(process("ta4jStorage", "Ta4j In-Memory Store", "Storage",
                "healthy", ta4jStorage.storageMetrics()));

        return processes;
    }

    private static Map<String, Object> process(
            String id, String name, String layer, String status, Map<String, ?> metrics) {
        Map<String, Object> p = new LinkedHashMap<>();
        p.put("id", id);
        p.put("name", name);
        p.put("layer", layer);
        p.put("status", status);
        p.put("metrics", new HashMap<>(metrics));
        return p;
    }

    private static double roundMb(long bytes) {
        return Math.round(bytes / 1024.0 / 1024.0 * 10) / 10.0;
    }

    private static double round2(double v) {
        return Math.round(v * 100) / 100.0;
    }
}
