package com.goldenchart.trading.pipeline;

import com.goldenchart.trading.TradingExecutionService;
import com.google.common.util.concurrent.RateLimiter;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 주문 전용 단일 쓰레드 + RateLimiter — 업비트 429 방지.
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class OrderExecutionQueue {

    private final TradingExecutionService tradingExecutionService;
    private final TradingPipelineProperties props;

    private final BlockingQueue<OrderRequest> queue = new LinkedBlockingQueue<>(4096);
    private final AtomicLong submitted = new AtomicLong();
    private final AtomicLong executed = new AtomicLong();
    private final AtomicLong failed = new AtomicLong();
    private final AtomicLong dropped = new AtomicLong();

    private RateLimiter rateLimiter;
    private volatile boolean running;
    private Thread worker;

    @PostConstruct
    void start() {
        if (!props.isEnabled()) return;
        rateLimiter = RateLimiter.create(Math.max(0.5, props.getOrderRatePerSecond()));
        running = true;
        worker = new Thread(this::runLoop, "order-execution");
        worker.setDaemon(true);
        worker.start();
        log.info("[OrderExecutionQueue] 시작 (rate={}/s)", props.getOrderRatePerSecond());
    }

    @PreDestroy
    void stop() {
        running = false;
        if (worker != null) worker.interrupt();
    }

    public void submit(OrderRequest req) {
        if (!props.isEnabled()) {
            executeNow(req);
            return;
        }
        if (!queue.offer(req)) {
            dropped.incrementAndGet();
            log.warn("[OrderExecutionQueue] 주문 큐 포화 — {} {}", req.market(), req.kind());
        } else {
            submitted.incrementAndGet();
        }
    }

    public void submitSignal(String deviceId, Long userId, String market,
                             Long strategyId, String signal, double price) {
        submit(OrderRequest.strategySignal(deviceId, userId, market, strategyId, signal, price));
    }

    private void runLoop() {
        while (running) {
            try {
                OrderRequest req = queue.poll(1, TimeUnit.SECONDS);
                if (req == null) continue;
                rateLimiter.acquire();
                executeNow(req);
                executed.incrementAndGet();
            } catch (InterruptedException e) {
                if (!running) break;
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                failed.incrementAndGet();
                log.warn("[OrderExecutionQueue] 실행 실패: {}", e.getMessage());
            }
        }
    }

    private void executeNow(OrderRequest req) {
        if (req.deviceId() == null || req.deviceId().isBlank()) return;
        if (!"BUY".equals(req.side()) && !"SELL".equals(req.side())) return;
        try {
            if (req.kind() == OrderRequest.OrderKind.STRATEGY_SIGNAL) {
                tradingExecutionService.executeSignal(
                        req.deviceId(), req.userId(), req.market(),
                        req.strategyId(), req.side(), req.price());
            } else {
                tradingExecutionService.executeRiskExit(req);
            }
            log.debug("[OrderExecutionQueue] {} {} {} ({})",
                    req.kind(), req.side(), req.market(), req.reason());
        } catch (Exception e) {
            log.warn("[OrderExecutionQueue] {} {}: {}", req.kind(), req.market(), e.getMessage());
        }
    }

    public boolean isWorkerAlive() {
        return worker != null && worker.isAlive();
    }

    public OrderQueueStats stats() {
        return new OrderQueueStats(
                queue.size(),
                submitted.get(),
                executed.get(),
                failed.get(),
                dropped.get(),
                isWorkerAlive(),
                props.isEnabled() ? props.getOrderRatePerSecond() : 0
        );
    }

    public record OrderQueueStats(
            int pending, long submitted, long executed, long failed, long dropped,
            boolean workerAlive, double ratePerSecond) {}
}
