package com.datacenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.datacenter.entity.stat.StatContractPnl;
import com.datacenter.mapper.StatContractPnlMapper;
import com.datacenter.service.StatContractPnlService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class StatContractPnlServiceImpl
        extends ServiceImpl<StatContractPnlMapper, StatContractPnl>
        implements StatContractPnlService {

    private static final int SELF = 0;   // 自主合约：follow_id IS NULL
    private static final int COPY = 1;   // 跟单合约：follow_id IS NOT NULL

    @Resource
    private RestTemplate restTemplate;

    @Value("${admin.host}")
    private String adminHost;

    @Override
    public void computeAndSaveBoth(LocalDate date) {
        computeAndSave(date, false);
        computeAndSave(date, true);
    }

    @Override
    public StatContractPnl computeAndSave(LocalDate date, boolean includeInternal) {
        int ii = includeInternal ? 1 : 0;
        log.info("[ContractPnl] 开始统计 date={} includeInternal={}", date, includeInternal);

        Map<String, BigDecimal> prices = fetchPrices();
        if (prices.isEmpty()) {
            log.warn("[ContractPnl] 无法获取实时价格，持仓盈亏将跳过本次计算");
        }

        LocalDateTime dayStart = LocalDateTime.of(date, LocalTime.MIDNIGHT);
        LocalDateTime dayEnd   = dayStart.plusDays(1);
        long startMs = dayStart.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        long endMs   = dayEnd.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();

        // ── 自主合约 ────────────────────────────────────────────────────────
        BigDecimal[] selfHold            = sumHoldPnl(baseMapper.listOpenPositions(SELF, ii), prices);
        BigDecimal selfHoldProfit        = selfHold[0];
        BigDecimal selfHoldLoss          = selfHold[1];
        BigDecimal selfTodayCloseProfit  = baseMapper.sumTodayCloseProfit(SELF, startMs, endMs, ii);
        BigDecimal selfTodayCloseLoss    = baseMapper.sumTodayCloseLoss(SELF, startMs, endMs, ii);
        BigDecimal selfTotalCloseProfit  = baseMapper.sumTotalCloseProfit(SELF, ii);
        BigDecimal selfTotalCloseLoss    = baseMapper.sumTotalCloseLoss(SELF, ii);

        // ── 跟单合约 ────────────────────────────────────────────────────────
        BigDecimal[] copyHold            = sumHoldPnl(baseMapper.listOpenPositions(COPY, ii), prices);
        BigDecimal copyHoldProfit        = copyHold[0];
        BigDecimal copyHoldLoss          = copyHold[1];
        BigDecimal copyTodayCloseProfit  = baseMapper.sumTodayCloseProfit(COPY, startMs, endMs, ii);
        BigDecimal copyTodayCloseLoss    = baseMapper.sumTodayCloseLoss(COPY, startMs, endMs, ii);
        BigDecimal copyTotalCloseProfit  = baseMapper.sumTotalCloseProfit(COPY, ii);
        BigDecimal copyTotalCloseLoss    = baseMapper.sumTotalCloseLoss(COPY, ii);

        log.info("[ContractPnl] date={} ii={} " +
                 "self: holdP={} holdL={} todayP={} todayL={} totalP={} totalL={} | " +
                 "copy: holdP={} holdL={} todayP={} todayL={} totalP={} totalL={}",
                 date, ii,
                 selfHoldProfit, selfHoldLoss, selfTodayCloseProfit, selfTodayCloseLoss,
                 selfTotalCloseProfit, selfTotalCloseLoss,
                 copyHoldProfit, copyHoldLoss, copyTodayCloseProfit, copyTodayCloseLoss,
                 copyTotalCloseProfit, copyTotalCloseLoss);

        StatContractPnl entity = getByDate(date, includeInternal);
        if (entity == null) {
            entity = new StatContractPnl();
            entity.setStatDate(date);
            entity.setIncludeInternal(ii);
        }
        entity.setSelfHoldProfit(selfHoldProfit);
        entity.setSelfHoldLoss(selfHoldLoss);
        entity.setSelfTodayCloseProfit(selfTodayCloseProfit);
        entity.setSelfTodayCloseLoss(selfTodayCloseLoss);
        entity.setSelfTotalCloseProfit(selfTotalCloseProfit);
        entity.setSelfTotalCloseLoss(selfTotalCloseLoss);
        entity.setCopyHoldProfit(copyHoldProfit);
        entity.setCopyHoldLoss(copyHoldLoss);
        entity.setCopyTodayCloseProfit(copyTodayCloseProfit);
        entity.setCopyTodayCloseLoss(copyTodayCloseLoss);
        entity.setCopyTotalCloseProfit(copyTotalCloseProfit);
        entity.setCopyTotalCloseLoss(copyTotalCloseLoss);
        entity.setUpdatedTime(LocalDateTime.now());

        saveOrUpdate(entity);
        log.info("[ContractPnl] 写入完成 id={}", entity.getId());
        return entity;
    }

    @Override
    public void backfill(int days) {
        if (days < 1)  days = 1;
        if (days > 60) days = 60;
        LocalDate today = LocalDate.now();
        for (int i = days; i >= 1; i--) {
            LocalDate date = today.minusDays(i);
            log.info("[ContractPnl] 回填历史统计 date={}", date);
            try {
                computeAndSaveBoth(date);
            } catch (Exception e) {
                log.error("[ContractPnl] 回填失败 date={}", date, e);
            }
        }
        log.info("[ContractPnl] 回填完成，共 {} 天", days);
    }

    @Override
    public StatContractPnl getByDate(LocalDate date, boolean includeInternal) {
        return getOne(new LambdaQueryWrapper<StatContractPnl>()
                .eq(StatContractPnl::getStatDate, date)
                .eq(StatContractPnl::getIncludeInternal, includeInternal ? 1 : 0));
    }

    /**
     * 从 Admin 代理接口拉取实时价格，格式：symbol → price。
     * 兼容 "ETH/USDT" 和 "ETHUSDT" 两种 key 格式（各存一份）。
     */
    private Map<String, BigDecimal> fetchPrices() {
        try {
            String url = adminHost + "/admin/noauth/swap-prices";
            ParameterizedTypeReference<Map<String, BigDecimal>> typeRef =
                    new ParameterizedTypeReference<Map<String, BigDecimal>>() {};
            ResponseEntity<Map<String, BigDecimal>> resp =
                    restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(null), typeRef);
            if (resp.getBody() != null && !resp.getBody().isEmpty()) {
                Map<String, BigDecimal> result = new java.util.HashMap<>(resp.getBody());
                // 同时写入无斜杠格式，方便两种 symbol 风格都能命中
                resp.getBody().forEach((k, v) -> result.put(k.replace("/", ""), v));
                log.info("[ContractPnl] 获取到 {} 个交易对价格", resp.getBody().size());
                return result;
            }
        } catch (Exception e) {
            log.warn("[ContractPnl] 获取实时价格失败: {}", e.getMessage());
        }
        return Collections.emptyMap();
    }

    /**
     * 用实时价格计算一组持仓的浮盈/浮亏合计。
     * 先按 member_id 汇总每人净盈亏，再分桶：净盈→profit，净亏→loss。
     * 与仓位管理页面保持一致（每人一行净值，不分正负分别累加）。
     * 返回 [holdProfit, holdLoss]，均为正数。
     */
    private BigDecimal[] sumHoldPnl(List<Map<String, Object>> positions, Map<String, BigDecimal> prices) {
        log.info("[ContractPnl] sumHoldPnl 持仓数量={}", positions.size());

        // 1. 计算每条持仓的 pnl，按 member_id 累加
        Map<Long, BigDecimal> memberNetMap = new java.util.LinkedHashMap<>();
        for (Map<String, Object> row : positions) {
            Long memberId    = ((Number) row.get("member_id")).longValue();
            String symbol    = (String) row.get("symbol");
            int direction    = ((Number) row.get("direction")).intValue();
            int moneyType    = ((Number) row.get("money_type")).intValue();
            BigDecimal openP = toBD(row.get("open_price"));
            BigDecimal qty   = toBD(row.get("avail_qty"));
            BigDecimal share = toBD(row.get("share_number"));

            BigDecimal curP = prices.getOrDefault(symbol,
                              prices.getOrDefault(symbol.replace("/", ""), BigDecimal.ZERO));
            if (curP.compareTo(BigDecimal.ZERO) <= 0 || openP.compareTo(BigDecimal.ZERO) <= 0) {
                log.info("[ContractPnl]   跳过 member={} symbol={} dir={} mt={} openP={} curP={} qty={}",
                         memberId, symbol, direction, moneyType, openP, curP, qty);
                continue;
            }

            BigDecimal priceDiff = direction == 0
                    ? curP.subtract(openP)
                    : openP.subtract(curP);

            BigDecimal pnl;
            if (moneyType == 0) {
                pnl = priceDiff.multiply(qty).multiply(share)
                        .divide(openP, 8, RoundingMode.HALF_UP);
            } else {
                pnl = priceDiff.multiply(qty).setScale(8, RoundingMode.HALF_UP);
            }

            log.info("[ContractPnl]   member={} symbol={} dir={} mt={} openP={} curP={} qty={} share={} pnl={}",
                     memberId, symbol, direction, moneyType, openP, curP, qty, share, pnl);

            memberNetMap.merge(memberId, pnl, BigDecimal::add);
        }

        // 2. 按每人净值分桶（与仓位管理页一致：整人净盈亏，不拆分正负持仓）
        BigDecimal profit = BigDecimal.ZERO;
        BigDecimal loss   = BigDecimal.ZERO;
        for (Map.Entry<Long, BigDecimal> entry : memberNetMap.entrySet()) {
            BigDecimal net = entry.getValue();
            log.info("[ContractPnl]   member={} net={}", entry.getKey(), net);
            if (net.compareTo(BigDecimal.ZERO) > 0) {
                profit = profit.add(net);
            } else if (net.compareTo(BigDecimal.ZERO) < 0) {
                loss = loss.add(net.abs());
            }
        }
        return new BigDecimal[]{profit, loss};
    }

    private BigDecimal toBD(Object v) {
        if (v == null) return BigDecimal.ZERO;
        if (v instanceof BigDecimal) return (BigDecimal) v;
        return new BigDecimal(v.toString());
    }
}
