package com.goldenchart.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.goldenchart.dto.LiveOrderRequest;
import com.goldenchart.dto.LiveSummaryDto;
import com.goldenchart.dto.LiveTradeDto;
import com.goldenchart.dto.UpbitApiCredentials;
import com.goldenchart.entity.GcAppSettings;
import com.goldenchart.entity.GcLiveTrade;
import com.goldenchart.repository.GcLiveTradeRepository;
import com.goldenchart.trading.TradingMode;
import com.goldenchart.upbit.UpbitOrderApiClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * 업비트 실거래 — 시그널·손절/익절 시 주문 실행.
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class LiveTradingService {

    private static final RoundingMode RM = RoundingMode.HALF_UP;
    private static final double MIN_ORDER_KRW = 5000;

    private final AppSettingsService appSettingsService;
    private final UpbitOrderApiClient upbitApi;
    private final GcLiveTradeRepository tradeRepo;

    @Transactional(readOnly = true)
    public LiveSummaryDto getSummary(Long userId, String deviceId) {
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        UpbitApiCredentials creds = appSettingsService.resolveUpbitCredentials(app);
        boolean configured = creds.isComplete();
        double krw = 0;
        List<LiveSummaryDto.Position> positions = new ArrayList<>();

        if (configured) {
            try {
                krw = upbitApi.getKrwBalance(creds.accessKey(), creds.secretKey());
                var accounts = upbitApi.getAccounts(creds.accessKey(), creds.secretKey());
                if (accounts != null && accounts.isArray()) {
                    for (JsonNode a : accounts) {
                        String cur = a.path("currency").asText();
                        if ("KRW".equals(cur)) continue;
                        double bal = a.path("balance").asDouble(0);
                        if (bal <= 0) continue;
                        double avg = a.path("avg_buy_price").asDouble(0);
                        positions.add(LiveSummaryDto.Position.builder()
                                .symbol("KRW-" + cur)
                                .quantity(bal)
                                .avgPrice(avg)
                                .build());
                    }
                }
            } catch (Exception e) {
                log.warn("[LiveTrading] summary 실패: {}", e.getMessage());
            }
        }

        return LiveSummaryDto.builder()
                .enabled(Boolean.TRUE.equals(app.getLiveAutoTradeEnabled()))
                .configured(configured)
                .tradingMode(app.getTradingMode() != null ? app.getTradingMode() : "PAPER")
                .krwBalance(krw)
                .positions(positions)
                .build();
    }

    @Transactional(readOnly = true)
    public List<LiveTradeDto> listTrades(Long userId, String deviceId) {
        String dev = resolveDeviceId(deviceId);
        return tradeRepo.findTop100ByDeviceIdOrderByCreatedAtDesc(dev)
                .stream().map(this::toDto).toList();
    }

    /**
     * 전략 시그널·SL/TP 에서 호출.
     */
    public void tryExecuteOnSignal(String deviceId, Long userId, String market,
                                   Long strategyId, String signalType, double price,
                                   String source) {
        if (deviceId == null || deviceId.isBlank()) return;
        if (!"BUY".equals(signalType) && !"SELL".equals(signalType)) return;

        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        TradingMode mode = TradingMode.fromString(app.getTradingMode());
        if (!mode.useLive()) return;
        if (!Boolean.TRUE.equals(app.getLiveAutoTradeEnabled())) return;
        UpbitApiCredentials creds = appSettingsService.resolveUpbitCredentials(app);
        if (!creds.isComplete()) {
            log.debug("[LiveTrading] API 키 없음 — skip {}", market);
            return;
        }
        if (!Boolean.TRUE.equals(app.getLiveStrategyCheck()) && "STRATEGY".equals(source)) return;

        String access = creds.accessKey();
        String secret = creds.secretKey();

        try {
            if ("BUY".equals(signalType)) {
                double budgetPct = app.getLiveAutoTradeBudgetPct() != null
                        ? app.getLiveAutoTradeBudgetPct().doubleValue()
                        : (app.getPaperAutoTradeBudgetPct() != null
                        ? app.getPaperAutoTradeBudgetPct().doubleValue() : 95);
                double krw = upbitApi.getKrwBalance(access, secret);
                double budget = krw * budgetPct / 100.0;
                if (budget < MIN_ORDER_KRW) {
                    log.debug("[LiveTrading] BUY skip: budget={}", budget);
                    return;
                }
                JsonNode res = upbitApi.placeMarketBuy(access, secret, market, budget);
                recordTrade(deviceId, userId, market, "BUY", source, strategyId, res, price, budget);
                log.info("[LiveTrading] BUY {} ~{} KRW", market, (long) budget);
            } else {
                double vol = upbitApi.getCoinBalance(access, secret, market);
                if (vol <= 0) {
                    log.debug("[LiveTrading] SELL skip: no balance {}", market);
                    return;
                }
                JsonNode res = upbitApi.placeMarketSell(access, secret, market, vol);
                recordTrade(deviceId, userId, market, "SELL", source, strategyId, res, price, vol);
                log.info("[LiveTrading] SELL {} vol={}", market, vol);
            }
        } catch (UpbitOrderApiClient.UpbitApiException e) {
            log.warn("[LiveTrading] 주문 실패 {} {}: {}", signalType, market, e.getMessage());
        }
    }

    /**
     * 알림 모달·수동 주문 — 시장가 매수/매도.
     */
    public LiveTradeDto placeManualOrder(Long userId, String deviceId, LiveOrderRequest req) {
        if (req == null || req.getMarket() == null || req.getSide() == null) {
            throw new IllegalArgumentException("market, side required");
        }
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        UpbitApiCredentials creds = appSettingsService.resolveUpbitCredentials(app);
        if (!creds.isComplete()) {
            throw new IllegalStateException("업비트 API 키가 등록되지 않았습니다.");
        }
        String access = creds.accessKey();
        String secret = creds.secretKey();
        String market = req.getMarket();
        String side = req.getSide().toUpperCase();
        String dev = resolveDeviceId(deviceId);

        try {
            if ("BUY".equals(side)) {
                double budget = req.getKrwAmount() != null && req.getKrwAmount() > 0
                        ? req.getKrwAmount()
                        : computeBuyBudget(app, access, secret);
                if (budget < MIN_ORDER_KRW) {
                    throw new IllegalStateException("주문 가능 금액이 부족합니다.");
                }
                JsonNode res = upbitApi.placeMarketBuy(access, secret, market, budget);
                recordTrade(dev, userId, market, "BUY", "MANUAL", req.getStrategyId(), res, 0, budget);
                return listTrades(userId, deviceId).get(0);
            }
            if ("SELL".equals(side)) {
                double vol = req.getQuantity() != null && req.getQuantity() > 0
                        ? req.getQuantity()
                        : upbitApi.getCoinBalance(access, secret, market);
                if (vol <= 0) throw new IllegalStateException("매도 가능 수량이 없습니다.");
                JsonNode res = upbitApi.placeMarketSell(access, secret, market, vol);
                recordTrade(dev, userId, market, "SELL", "MANUAL", req.getStrategyId(), res, 0, vol);
                return listTrades(userId, deviceId).get(0);
            }
            throw new IllegalArgumentException("side must be BUY or SELL");
        } catch (UpbitOrderApiClient.UpbitApiException e) {
            throw new IllegalStateException("업비트 주문 실패: " + e.getMessage(), e);
        }
    }

    private double computeBuyBudget(GcAppSettings app, String access, String secret) {
        double budgetPct = app.getLiveAutoTradeBudgetPct() != null
                ? app.getLiveAutoTradeBudgetPct().doubleValue()
                : (app.getPaperAutoTradeBudgetPct() != null
                ? app.getPaperAutoTradeBudgetPct().doubleValue() : 95);
        try {
            double krw = upbitApi.getKrwBalance(access, secret);
            return krw * budgetPct / 100.0;
        } catch (Exception e) {
            log.warn("[LiveTrading] balance read failed: {}", e.getMessage());
            return 0;
        }
    }

    public boolean hasApiKeys(GcAppSettings app) {
        return appSettingsService.hasUpbitApiKeys(app);
    }

    private void recordTrade(String deviceId, Long userId, String market, String side,
                             String source, Long strategyId, JsonNode res,
                             double refPrice, double amountOrVol) {
        String uuid = res != null ? res.path("uuid").asText(null) : null;
        double executed = res != null ? res.path("executed_volume").asDouble(amountOrVol) : amountOrVol;
        double paid = res != null ? res.path("paid_fee").asDouble(0) : 0;
        BigDecimal gross = "BUY".equals(side)
                ? BigDecimal.valueOf(amountOrVol).setScale(2, RM)
                : BigDecimal.valueOf(refPrice * executed).setScale(2, RM);

        tradeRepo.save(GcLiveTrade.builder()
                .deviceId(resolveDeviceId(deviceId))
                .userId(userId)
                .symbol(market)
                .side(side)
                .orderKind("market")
                .source(source)
                .strategyId(strategyId)
                .upbitOrderUuid(uuid)
                .price(BigDecimal.valueOf(refPrice).setScale(8, RM))
                .quantity(BigDecimal.valueOf(executed).setScale(12, RM))
                .grossAmount(gross)
                .feeAmount(BigDecimal.valueOf(paid).setScale(2, RM))
                .state(res != null ? res.path("state").asText("done") : "done")
                .build());
    }

    private LiveTradeDto toDto(GcLiveTrade t) {
        return LiveTradeDto.builder()
                .id(t.getId())
                .symbol(t.getSymbol())
                .side(t.getSide())
                .source(t.getSource())
                .price(t.getPrice().doubleValue())
                .quantity(t.getQuantity().doubleValue())
                .grossAmount(t.getGrossAmount().doubleValue())
                .upbitOrderUuid(t.getUpbitOrderUuid())
                .createdAt(t.getCreatedAt() != null ? t.getCreatedAt().toString() : null)
                .build();
    }

    private static String resolveDeviceId(String deviceId) {
        return deviceId != null && !deviceId.isBlank() ? deviceId : "anonymous";
    }
}
