package com.spot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spot.constant.RedisKey;
import com.spot.dto.AccountAssetDto;
import com.spot.entity.Account;
import com.spot.entity.AccountRecord;
import com.spot.entity.ConfigCoinSymbol;
import com.spot.enums.AccountRecordType;
import com.spot.resp.AccountListResp;
import com.spot.enums.MessageCode;
import com.spot.exception.BizException;
import com.spot.service.AccountAssetService;
import com.spot.service.AccountRecordService;
import com.spot.service.AccountService;
import com.spot.service.ConfigCoinSymbolService;
import com.spot.utils.Constant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 账户资产服务实现类
 *
 * @author jean
 * @date 2026-05-06
 */
@Slf4j
@Service
public class AccountAssetServiceImpl implements AccountAssetService {

    @Autowired
    private AccountService accountService;

    @Autowired
    private ConfigCoinSymbolService configCoinSymbolService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private AccountRecordService accountRecordService;

    /**
     * 获取用户资产列表，按 USDT 价值降序排列
     *
     * @param uid      用户ID
     * @param hideZero true=隐藏余额为0的账户；false=展示所有开启币种（内存补全0余额）
     * @return 资产列表响应
     */
    @Override
    public AccountListResp getUserAssetList(Long uid, boolean hideZero) {
        if (uid == null) {
            throw new BizException(MessageCode.USER_ID_EMPTY);
        }

        // 1. 按模式获取资产列表
        List<AccountAssetDto> assetList;
        if (hideZero) {
            assetList = buildHideZeroAssetList(uid);
        } else {
            assetList = buildShowAllAssetList(uid);
        }

        // 2. 计算总金额
        AccountListResp resp = new AccountListResp();
        int precision = getUsdtPrecision();
        BigDecimal totalAmount = assetList.stream()
                .map(AccountAssetDto::getUsdtAmount)
                .filter(amount -> amount != null && amount.compareTo(BigDecimal.ZERO) > 0)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        resp.setAssetList(assetList);
        BigDecimal scaledTotal = totalAmount.setScale(precision, RoundingMode.DOWN);
        resp.setTotalAmount(scaledTotal);

        // 若任何非USDT且余额>0的资产缺失价格，跳过盈亏计算，避免产生虚假大额亏损
        boolean allPricesAvailable = assetList.stream()
                .filter(dto -> !Constant.COIN_SYMBOL_USDT.equalsIgnoreCase(dto.getSymbol()))
                .filter(dto -> dto.getTotal() != null && dto.getTotal().compareTo(BigDecimal.ZERO) > 0)
                .allMatch(dto -> dto.getUsdtAmount() != null && dto.getUsdtAmount().compareTo(BigDecimal.ZERO) > 0);

        // ── 今日盈亏：昨日快照 → 实时差值 ────────────────────────────────
        calcAndSaveDailyPnl(uid, scaledTotal, resp, precision, allPricesAvailable);

        return resp;
    }

    /**
     * 隐藏零余额模式：DB层过滤 hot_money+cold_money+withdraw_money > 0，仅返回有余额的账户
     *
     * @param uid 用户ID
     * @return 资产DTO列表（已排序）
     */
    private List<AccountAssetDto> buildHideZeroAssetList(Long uid) {
        // 查询余额大于0的账户（DB层过滤）
        List<Account> accountList = accountService.list(
                new LambdaQueryWrapper<Account>()
                        .eq(Account::getUid, uid)
                        .apply("hot_money + cold_money + withdraw_money > 0"));

        if (accountList == null || accountList.isEmpty()) {
            return new ArrayList<>();
        }

        return accountList.stream()
                .filter(account -> account != null && account.getSymbol() != null)
                .map(this::buildAssetDto)
                .filter(dto -> dto != null && dto.getTotal().compareTo(BigDecimal.ZERO) > 0)
                .sorted(this::compareByUsdtAmountDesc)
                .collect(Collectors.toList());
    }

    /**
     * 显示全部模式：从内存中获取所有开启币种，已有账户取实际余额，缺失账户赋值0返回
     * 不向 DB 写入任何新记录
     *
     * @param uid 用户ID
     * @return 资产DTO列表（已排序）
     */
    private List<AccountAssetDto> buildShowAllAssetList(Long uid) {
        // 1. 查询用户已有账户，构建 symbol → account 映射
        List<Account> accountList = accountService.list(
                new LambdaQueryWrapper<Account>().eq(Account::getUid, uid));
        Map<String, Account> accountMap = accountList == null ? new java.util.HashMap<>() :
                accountList.stream()
                        .filter(a -> a != null && a.getSymbol() != null)
                        .collect(Collectors.toMap(Account::getSymbol, a -> a, (a, b) -> a));

        // 2. 遍历所有开启币种，有账户取实际余额，无账户补0余额DTO
        List<ConfigCoinSymbol> openCoins = configCoinSymbolService.getOpenCoinSymbols();
        if (openCoins == null || openCoins.isEmpty()) {
            return new ArrayList<>();
        }

        return openCoins.stream()
                .filter(coin -> coin.getCoinSymbol() != null)
                .map(coin -> {
                    Account account = accountMap.get(coin.getCoinSymbol());
                    if (account != null) {
                        return buildAssetDto(account);
                    } else {
                        return buildZeroAssetDto(coin.getCoinSymbol());
                    }
                })
                .filter(dto -> dto != null)
                .sorted(this::compareByUsdtAmountDesc)
                .collect(Collectors.toList());
    }

    /** 构建余额全为0的资产DTO（用于展示开启但未持有的币种） */
    private AccountAssetDto buildZeroAssetDto(String symbol) {
        BigDecimal rate = getRate(symbol);
        AccountAssetDto dto = new AccountAssetDto();
        dto.setSymbol(symbol);
        dto.setAvailable(BigDecimal.ZERO);
        dto.setFrozen(BigDecimal.ZERO);
        dto.setWithdraw(BigDecimal.ZERO);
        dto.setTotal(BigDecimal.ZERO);
        dto.setRate(rate);
        dto.setAvailableUsdt(BigDecimal.ZERO);
        dto.setFrozenUsdt(BigDecimal.ZERO);
        dto.setWithdrawUsdt(BigDecimal.ZERO);
        dto.setUsdtAmount(BigDecimal.ZERO);
        return dto;
    }

    /** 按 USDT 估值降序比较（用于排序） */
    private int compareByUsdtAmountDesc(AccountAssetDto a, AccountAssetDto b) {
        BigDecimal amountA = a.getUsdtAmount() != null ? a.getUsdtAmount() : BigDecimal.ZERO;
        BigDecimal amountB = b.getUsdtAmount() != null ? b.getUsdtAmount() : BigDecimal.ZERO;
        return amountB.compareTo(amountA);
    }

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd");

    /**
     * 计算并写入今日盈亏。
     * 公式：todayPnl = currentTotal - baseline - todayNetInflow
     *   baseline     = 昨日快照（日 2+ 用户）或 0（首日用户）
     *   todayNetInflow = 今日划转入 - 划转出（account_record type=1/2），排除非价格变动的资金流动
     *   effectiveCapital = baseline + todayNetInflow，用于前端合并盈亏率分母
     */
    private void calcAndSaveDailyPnl(Long uid, BigDecimal currentTotal,
                                      AccountListResp resp, int precision,
                                      boolean allPricesAvailable) {
        try {
            String today     = LocalDate.now().format(DATE_FMT);
            String yesterday = LocalDate.now().minusDays(1).format(DATE_FMT);

            String keyToday     = RedisKey.PNL_SNAPSHOT_PREFIX + uid + ":" + today;
            String keyYesterday = RedisKey.PNL_SNAPSHOT_PREFIX + uid + ":" + yesterday;

            // 持续更新今日快照，确保次日 keyYesterday 取到今日最新市值
            redisTemplate.opsForValue().set(keyToday, currentTotal.toPlainString(), 48, TimeUnit.HOURS);

            // 昨日快照作为基线（首日用户为 null → baseline = 0）
            BigDecimal baseline = BigDecimal.ZERO;
            String yesterdayStr = redisTemplate.opsForValue().get(keyYesterday);
            if (StringUtils.isNotBlank(yesterdayStr)) {
                try {
                    BigDecimal yt = new BigDecimal(yesterdayStr);
                    if (yt.compareTo(BigDecimal.ZERO) > 0) baseline = yt;
                } catch (NumberFormatException ignored) {}
            }

            // 今日净划转额（划转入 - 划转出），换算为 USDT，用于剔除非价格盈亏
            BigDecimal todayNetInflow = calcTodayNetInflowUsdt(uid);

            // effectiveCapital = 投入资本，供前端合并盈亏率使用
            BigDecimal effectiveCapital = baseline.add(todayNetInflow);
            resp.setYesterdayTotal(effectiveCapital.compareTo(BigDecimal.ZERO) > 0 ? effectiveCapital : null);

            if (!allPricesAvailable) {
                resp.setTodayPnl(BigDecimal.ZERO);
                resp.setTodayPnlRate(null);
                return;
            }

            BigDecimal todayPnl     = BigDecimal.ZERO;
            BigDecimal todayPnlRate = null;
            if (effectiveCapital.compareTo(BigDecimal.ZERO) > 0) {
                todayPnl     = currentTotal.subtract(baseline).subtract(todayNetInflow)
                                           .setScale(precision, RoundingMode.HALF_UP);
                todayPnlRate = todayPnl.divide(effectiveCapital, 4, RoundingMode.HALF_UP);
            }

            resp.setTodayPnl(todayPnl);
            resp.setTodayPnlRate(todayPnlRate);
        } catch (Exception e) {
            log.warn("calcAndSaveDailyPnl failed uid={}: {}", uid, e.getMessage());
            resp.setTodayPnl(BigDecimal.ZERO);
            resp.setTodayPnlRate(null);
        }
    }

    /**
     * 查询今日净划转额（划转入 - 划转出），折算为 USDT。
     * 用于从 P&L 中剔除资金划转带来的虚假盈亏。
     */
    private BigDecimal calcTodayNetInflowUsdt(Long uid) {
        try {
            LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
            List<AccountRecord> records = accountRecordService.list(
                    new LambdaQueryWrapper<AccountRecord>()
                            .eq(AccountRecord::getUid, uid)
                            .in(AccountRecord::getType, Arrays.asList(
                                    AccountRecordType.TRANSFER_IN.getCode(),
                                    AccountRecordType.TRANSFER_OUT.getCode()))
                            .eq(AccountRecord::getIsChanges, 1)
                            .ge(AccountRecord::getCtime, startOfDay));

            BigDecimal net = BigDecimal.ZERO;
            for (AccountRecord r : records) {
                BigDecimal rate = getRate(r.getSymbol());
                if (rate == null || r.getAmount() == null) continue;
                BigDecimal usdtVal = r.getAmount().multiply(rate);
                if (AccountRecordType.TRANSFER_IN.getCode().equals(r.getType())) {
                    net = net.add(usdtVal);
                } else {
                    net = net.subtract(usdtVal);
                }
            }
            return net;
        } catch (Exception e) {
            log.warn("calcTodayNetInflowUsdt failed uid={}: {}", uid, e.getMessage());
            return BigDecimal.ZERO;
        }
    }

    /**
     * 将 Account 转换为资产 DTO
     */
    private AccountAssetDto buildAssetDto(Account account) {
        BigDecimal available = account.getHotMoney() != null ? account.getHotMoney() : BigDecimal.ZERO;
        BigDecimal frozen = account.getColdMoney() != null ? account.getColdMoney() : BigDecimal.ZERO;
        BigDecimal withdraw = account.getWithdrawMoney() != null ? account.getWithdrawMoney() : BigDecimal.ZERO;
        BigDecimal total = available.add(frozen).add(withdraw);

        BigDecimal rate = getRate(account.getSymbol());
        int precision = getUsdtPrecision();

        AccountAssetDto dto = new AccountAssetDto();
        dto.setSymbol(account.getSymbol());
        dto.setAvailable(available);
        dto.setFrozen(frozen);
        dto.setWithdraw(withdraw);
        dto.setTotal(total);
        dto.setRate(rate);

        if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
            dto.setAvailableUsdt(available.multiply(rate).setScale(precision, RoundingMode.DOWN));
            dto.setFrozenUsdt(frozen.multiply(rate).setScale(precision, RoundingMode.DOWN));
            dto.setWithdrawUsdt(withdraw.multiply(rate).setScale(precision, RoundingMode.DOWN));
            dto.setUsdtAmount(total.multiply(rate).setScale(precision, RoundingMode.DOWN));
        } else {
            dto.setAvailableUsdt(BigDecimal.ZERO);
            dto.setFrozenUsdt(BigDecimal.ZERO);
            dto.setWithdrawUsdt(BigDecimal.ZERO);
            dto.setUsdtAmount(BigDecimal.ZERO);
        }

        return dto;
    }

    /**
     * 获取币种对 USDT 的汇率（带5秒本地缓存）
     */
    private BigDecimal getRate(String symbol) {
        if (StringUtils.isBlank(symbol)) {
            return null;
        }
        if (Constant.COIN_SYMBOL_USDT.equalsIgnoreCase(symbol)) {
            return BigDecimal.ONE;
        }

        BigDecimal cached = Constant.ACCOUNT_ASSET_RATE_CACHE.getIfPresent(symbol);
        if (cached != null) {
            return cached;
        }

        // 从 Redis 获取最新价格
        String symbolPair = (symbol + Constant.COIN_SYMBOL_USDT).toLowerCase();
        Object value = redisTemplate.opsForHash().get(RedisKey.LATEST_PRICE, symbolPair);
        if (value == null || StringUtils.isBlank(value.toString())) {
            return null;
        }
        try {
            BigDecimal price = new BigDecimal(value.toString().trim());
            if (price.compareTo(BigDecimal.ZERO) <= 0) {
                return null;
            }
            Constant.ACCOUNT_ASSET_RATE_CACHE.put(symbol, price);
            return price;
        } catch (NumberFormatException e) {
            log.warn("解析汇率失败: symbol={}, value={}", symbol, value);
            return null;
        }
    }

    /**
     * 获取 USDT 的显示精度
     */
    private int getUsdtPrecision() {
        try {
            ConfigCoinSymbol usdtConfig = configCoinSymbolService.getConfigCoinSymbol(Constant.COIN_SYMBOL_USDT);
            if (usdtConfig != null && usdtConfig.getShowPrecision() != null && usdtConfig.getShowPrecision() > 0) {
                return usdtConfig.getShowPrecision();
            }
        } catch (Exception e) {
            log.warn("获取USDT精度失败，使用默认精度: {}", e.getMessage());
        }
        return Constant.DEFAULT_PRECISION;
    }
}
