package com.spot.service.impl;

import com.spot.entity.Account;
import com.spot.entity.AccountRecord;
import com.spot.entity.ConfigCoinSymbol;
import com.spot.entity.MemberTransaction;
import com.spot.entity.MemberWallet;
import com.spot.entity.WalletTransRecord;
import com.spot.mapper.WalletTransRecordMapper;
import com.spot.enums.AccountRecordIsChanges;
import com.spot.enums.AccountRecordOperateType;
import com.spot.enums.AccountRecordStatus;
import com.spot.enums.AccountRecordType;
import com.spot.enums.MessageCode;
import com.spot.enums.TransactionType;
import com.spot.enums.TransferDirection;
import com.spot.req.TransferReq;
import com.spot.resp.ApiResultVo;
import com.spot.service.AccountRecordService;
import com.spot.service.AccountService;
import com.spot.service.ConfigCoinSymbolService;
import com.spot.service.MemberTransactionService;
import com.spot.service.AssetChangeMqService;
import com.spot.service.MemberWalletService;
import com.spot.service.TransferService;
import com.spot.utils.Constant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;

// WalletType ordinal 常量（与 dage WalletType 枚举保持一致）

/**
 * 账户划转服务实现类
 * <p>
 * 资金账户（member_wallet）与现货账户（account）双向划转，
 * 全程在同一事务内完成，account_record 和 member_transaction 均记录流水。
 *
 * @author jean
 * @date 2026-05-08
 */
@Slf4j
@Service
public class TransferServiceImpl implements TransferService {

    @Autowired
    private MemberWalletService memberWalletService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private AccountRecordService accountRecordService;

    @Autowired
    private MemberTransactionService memberTransactionService;

    @Autowired
    private ConfigCoinSymbolService configCoinSymbolService;

    @Autowired
    private AssetChangeMqService assetChangeMqService;

    @Autowired
    private WalletTransRecordMapper walletTransRecordMapper;

    /** WalletType ordinal：SPOT=资金账户，EXCHANGE=现货账户（与 dage WalletType 枚举一致） */
    private static final int WALLET_TYPE_SPOT     = 0;
    private static final int WALLET_TYPE_EXCHANGE = 5;

    /**
     * 执行账户划转（事务入口）
     * 先完成所有业务校验，校验通过后执行原子性数据库操作，任意步骤失败抛异常触发全量回滚。
     *
     * @param uid  用户ID
     * @param req  划转请求
     * @param lang 语言标识
     * @return 操作结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public ApiResultVo<Void> transfer(Long uid, TransferReq req, String lang) {
        // 1. 参数基础校验
        ApiResultVo<Void> paramCheck = validateParams(uid, req, lang);
        if (paramCheck != null) {
            return paramCheck;
        }

        String symbol = req.getSymbol().toUpperCase().trim();
        BigDecimal amount = req.getAmount();
        TransferDirection direction = TransferDirection.fromCode(req.getDirection());

        // 2. 路由到具体划转方向
        if (TransferDirection.FUND_TO_SPOT.equals(direction)) {
            return doFundToSpot(uid, symbol, amount, lang);
        } else {
            return doSpotToFund(uid, symbol, amount, lang);
        }
    }

    // ------------------------------------------------------------------ 核心业务

    /**
     * 资金账户 → 现货账户
     * 流程：校验钱包 → 扣减 member_wallet.balance → 初始化/获取现货账户
     *       → 增加 account.hot_money → 记录双方流水
     *
     * @param uid    用户ID
     * @param symbol 币种
     * @param amount 金额
     * @param lang   语言
     * @return 操作结果
     */
    private ApiResultVo<Void> doFundToSpot(Long uid, String symbol, BigDecimal amount, String lang) {
        // 1. 查询或初始化资金钱包
        MemberWallet wallet = memberWalletService.getOrCreateWallet(uid, symbol);
        if (Constant.WALLET_IS_LOCK_YES.equals(wallet.getIsLock())) {
            return ApiResultVo.fail(lang, MessageCode.WALLET_LOCKED);
        }
        if (wallet.getBalance() == null || wallet.getBalance().compareTo(amount) < 0) {
            return ApiResultVo.fail(lang, MessageCode.INSUFFICIENT_BALANCE);
        }

        // 2. 扣减资金钱包余额（乐观锁，失败抛异常触发回滚）
        int deductRows = memberWalletService.deductBalance(uid, symbol, amount);
        if (deductRows == 0) {
            throw new RuntimeException(
                    String.format("扣减资金钱包余额失败 - uid=%d, symbol=%s, amount=%s", uid, symbol, amount));
        }

        // 3. 获取或初始化现货账户
        Account spotAccount = accountService.getOrCreateAccount(uid, symbol);

        // 4. 增加现货账户可用余额
        int addRows = accountService.addAvailableBalance(spotAccount.getId(), amount);
        if (addRows == 0) {
            throw new RuntimeException(
                    String.format("增加现货账户余额失败 - accountId=%d, amount=%s", spotAccount.getId(), amount));
        }

        // 5. 记录资金流水（member_transaction）：资金账户转出
        memberTransactionService.save(
                buildFundTransaction(uid, symbol, amount, Constant.TRANSACTION_DIRECTION_OUT));

        // 6. 记录现货流水（account_record）：现货账户转入
        accountRecordService.save(
                buildSpotRecord(uid, spotAccount.getId(), symbol, amount,
                        AccountRecordType.TRANSFER_IN, AccountRecordOperateType.ADD));

        // 7. 写入 wallet_trans_record，供 ucenter-api asset/transfer/list 查询
        saveWalletTransRecord(uid, symbol, amount, WALLET_TYPE_SPOT, WALLET_TYPE_EXCHANGE);

        log.info("资金→现货划转成功 - uid={}, symbol={}, amount={}", uid, symbol, amount);
        return ApiResultVo.success(lang);
    }

    /**
     * 现货账户 → 资金账户
     * 流程：校验现货账户 → 扣减 account.hot_money → 校验资金钱包
     *       → 增加 member_wallet.balance → 记录双方流水
     *
     * @param uid    用户ID
     * @param symbol 币种
     * @param amount 金额
     * @param lang   语言
     * @return 操作结果
     */
    private ApiResultVo<Void> doSpotToFund(Long uid, String symbol, BigDecimal amount, String lang) {
        // 1. 查询并校验现货账户
        Account spotAccount = accountService.getOrCreateAccount(uid, symbol);
        if (spotAccount.getHotMoney() == null || spotAccount.getHotMoney().compareTo(amount) < 0) {
            return ApiResultVo.fail(lang, MessageCode.INSUFFICIENT_BALANCE);
        }

        // 2. 查询或初始化资金钱包（提前校验状态，避免扣减后才发现目标钱包异常）
        MemberWallet wallet = memberWalletService.getOrCreateWallet(uid, symbol);
        if (Constant.WALLET_IS_LOCK_YES.equals(wallet.getIsLock())) {
            return ApiResultVo.fail(lang, MessageCode.WALLET_LOCKED);
        }

        // 3. 扣减现货账户可用余额（SQL 内置 hot_money >= amount 保护）
        int deductRows = accountService.deductAvailableBalance(spotAccount.getId(), amount);
        if (deductRows == 0) {
            // hot_money 可能在预检后被并发消耗
            return ApiResultVo.fail(lang, MessageCode.INSUFFICIENT_BALANCE);
        }

        // 4. 增加资金钱包余额（失败抛异常触发回滚，已扣现货余额一并撤销）
        int addRows = memberWalletService.addBalance(uid, symbol, amount);
        if (addRows == 0) {
            throw new RuntimeException(
                    String.format("增加资金钱包余额失败 - uid=%d, symbol=%s, amount=%s", uid, symbol, amount));
        }

        // 5. 记录现货流水（account_record）：现货账户转出
        accountRecordService.save(
                buildSpotRecord(uid, spotAccount.getId(), symbol, amount,
                        AccountRecordType.TRANSFER_OUT, AccountRecordOperateType.SUBTRACT));

        // 6. 记录资金流水（member_transaction）：资金账户转入
        memberTransactionService.save(
                buildFundTransaction(uid, symbol, amount, Constant.TRANSACTION_DIRECTION_IN));

        // 7. 写入 wallet_trans_record，供 ucenter-api asset/transfer/list 查询
        saveWalletTransRecord(uid, symbol, amount, WALLET_TYPE_EXCHANGE, WALLET_TYPE_SPOT);

        log.info("现货→资金划转成功 - uid={}, symbol={}, amount={}", uid, symbol, amount);
        return ApiResultVo.success(lang);
    }

    // ------------------------------------------------------------------ 构建方法

    /**
     * 构建资金流水记录（member_transaction）
     *
     * @param uid       用户ID
     * @param symbol    币种
     * @param amount    金额
     * @param direction 方向：0=转入 1=转出（见 Constant.TRANSACTION_DIRECTION_*）
     * @return 流水实体
     */
    private MemberTransaction buildFundTransaction(Long uid, String symbol,
                                                    BigDecimal amount, int direction) {
        MemberTransaction tx = new MemberTransaction();
        tx.setMemberId(uid);
        tx.setSymbol(symbol);
        BigDecimal signedAmount = Constant.TRANSACTION_DIRECTION_OUT == direction
                ? amount.negate() : amount;
        tx.setAmount(signedAmount);
        tx.setFee(BigDecimal.ZERO);
        tx.setType(TransactionType.TRANSFER_ACCOUNTS.getOrdinal());
        tx.setDirection(direction);
        tx.setFlag(0);
        tx.setAddress(Constant.TRANSACTION_DIRECTION_IN == direction ? "SPOT_IN" : "SPOT_OUT");
        tx.setCreateTime(new Date());
        return tx;
    }

    /**
     * 构建现货账户流水记录（account_record）
     *
     * @param uid         用户ID
     * @param accountId   现货账户ID
     * @param symbol      币种
     * @param amount      金额
     * @param type        流水类型（TRANSFER_IN / TRANSFER_OUT）
     * @param operateType 操作类型（ADD / SUBTRACT）
     * @return 流水实体
     */
    private AccountRecord buildSpotRecord(Long uid, Long accountId, String symbol,
                                           BigDecimal amount, AccountRecordType type,
                                           AccountRecordOperateType operateType) {
        AccountRecord record = new AccountRecord();
        record.setUid(uid);
        record.setAccountId(accountId);
        record.setSymbol(symbol);
        record.setAmount(amount);
        record.setType(type.getCode());
        record.setOperateType(operateType.getCode());
        record.setStatus(AccountRecordStatus.COMPLETED.getCode());
        record.setIsChanges(AccountRecordIsChanges.YES.getCode());
        record.setSource(Constant.ACCOUNT_RECORD_SOURCE_ACCOUNT_TRANSFER);
        record.setCtime(LocalDateTime.now());
        return record;
    }

    /**
     * 写入 wallet_trans_record 划转记录
     * <p>
     * 与主事务同处一个事务内，主事务回滚时此记录也随之回滚。
     * 写入失败仅记录日志，不影响划转结果（try-catch 在事务外不拦截，
     * 实际上 MyBatis-Plus insert 失败会抛异常触发整体回滚，符合预期）。
     * </p>
     *
     * @param uid    用户ID
     * @param symbol 币种
     * @param amount 划转金额
     * @param source 转出账户类型（WalletType ordinal）
     * @param target 转入账户类型（WalletType ordinal）
     */
    private void saveWalletTransRecord(Long uid, String symbol, BigDecimal amount,
                                        int source, int target) {
        WalletTransRecord record = buildWalletTransRecord(uid, symbol, amount, source, target);
        walletTransRecordMapper.insert(record);
    }

    /**
     * 构建 WalletTransRecord 实体
     *
     * @param uid    用户ID
     * @param symbol 币种
     * @param amount 划转金额
     * @param source 转出账户类型（WalletType ordinal）
     * @param target 转入账户类型（WalletType ordinal）
     * @return 划转记录实体
     */
    private WalletTransRecord buildWalletTransRecord(Long uid, String symbol, BigDecimal amount,
                                                      int source, int target) {
        WalletTransRecord record = new WalletTransRecord();
        record.setMemberId(uid);
        record.setUnit(symbol);
        record.setAmount(amount);
        record.setSource(source);
        record.setTarget(target);
        record.setCreateTime(new Date());
        return record;
    }

    // ------------------------------------------------------------------ 校验方法

    /**
     * 基础参数校验（含现货开放币种校验）
     * 通过返回 null，失败返回对应的错误响应
     *
     * @param uid  用户ID
     * @param req  划转请求
     * @param lang 语言
     * @return null 表示校验通过；非 null 表示校验失败的错误响应
     */
    private ApiResultVo<Void> validateParams(Long uid, TransferReq req, String lang) {
        if (uid == null) {
            return ApiResultVo.fail(lang, MessageCode.PARAMETER_ERROR, "用户ID不能为空");
        }
        if (req.getSymbol() == null || req.getSymbol().trim().isEmpty()) {
            return ApiResultVo.fail(lang, MessageCode.PARAMETER_ERROR, "币种不能为空");
        }
        if (req.getAmount() == null || req.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            return ApiResultVo.fail(lang, MessageCode.TRANSFER_AMOUNT_INVALID);
        }
        if (TransferDirection.fromCode(req.getDirection()) == null) {
            return ApiResultVo.fail(lang, MessageCode.TRANSFER_DIRECTION_INVALID);
        }

        // 校验该币种是否在现货开放列表中
        String symbol = req.getSymbol().toUpperCase().trim();
        ConfigCoinSymbol coinConfig = configCoinSymbolService.getConfigCoinSymbol(symbol);
        if (coinConfig == null || coinConfig.getIsOpen() != Constant.IS_OPEN_ENABLED) {
            return ApiResultVo.fail(lang, MessageCode.COIN_SYMBOL_NOT_OPEN);
        }

        return null;
    }
}
