package com.bizzan.bitrade.service;

import com.bizzan.bitrade.dao.ExperienceGiftProductCoinRepository;
import com.bizzan.bitrade.dao.ExperienceGiftProductDao;
import com.bizzan.bitrade.dao.ExperienceGiftRecordDao;
import com.bizzan.bitrade.dao.ExperienceGiftRecordRepository;
import com.bizzan.bitrade.dao.ExperienceGiftWalletDao;
import com.bizzan.bitrade.entity.*;
import com.bizzan.bitrade.vendor.provider.support.EmailProvider;
import freemarker.template.Configuration;
import freemarker.template.Template;
import com.bizzan.bitrade.service.Base.TopBaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import com.bizzan.bitrade.util.ExperienceGiftRecordCache;
import java.math.BigDecimal;
import java.io.IOException;
import java.util.*;
import java.util.Map;
import freemarker.template.TemplateException;

@Slf4j
@Service
public class ExperienceGiftRecordService extends TopBaseService<ExperienceGiftRecord, ExperienceGiftRecordDao> {

    @Autowired
    @Override
    public void setDao(ExperienceGiftRecordDao dao) {
        super.setDao(dao);
    }

    @Autowired
    private ExperienceGiftRecordRepository recordRepository;

    @Autowired
    private ExperienceGiftProductDao productRepository;

    @Autowired
    private ExperienceGiftProductCoinRepository productCoinRepository;

    @Autowired
    private ExperienceGiftWalletDao experienceGiftWalletDao;

    @Autowired
    private ExperienceGiftRecordCache experienceGiftRecordCache;

    @Autowired
    private MemberService memberService;

    @Autowired
    private EmailProvider emailProvider;

    @Autowired
    private SysConfigService sysConfigService;

    private static final String EXPERIENCE_GIFT_GRANT_TEMPLATE = "experienceGiftGrant.ftl";

    // ----------------------------------------------------------------
    // 开仓流程方法（contract-swap-api 使用）
    // ----------------------------------------------------------------

    public Optional<ExperienceGiftRecord> findByIdAndMemberId(Long id, Long memberId) {
        return recordRepository.findByIdAndMemberId(id, memberId);
    }


    /**
     * 检查同交易对反方向是否已有持仓中的体验金（防双向套利）
     * 同方向允许开多个仓位，仅禁止同时持有反向仓位
     *
     * @param memberId   用户ID
     * @param coinSymbol 交易对
     * @param direction  本次开仓方向
     * @return true 表示反方向已有持仓，应拒绝开仓
     */
    public boolean hasOppositeActivePosition(Long memberId, String coinSymbol, ContractOrderDirection direction) {
        // 取反方向
        ContractOrderDirection opposite = (direction == ContractOrderDirection.BUY)
                ? ContractOrderDirection.SELL
                : ContractOrderDirection.BUY;
        return recordRepository.countByMemberCoinStatusAndDirection(
                memberId, coinSymbol, ExperienceGiftRecordStatus.IN_USE, opposite) > 0;
    }

    /**
     * 开仓成功后激活券，写入仓位ID、手续费、触发价等
     */
    public boolean activate(Long recordId, Long positionId, String coinSymbol, ContractOrderDirection direction,
                            BigDecimal openFee, BigDecimal autoClosePrice) {
        Date now = new Date();
        int rows = recordRepository.activateRecord(recordId, positionId, coinSymbol, direction,
                openFee, autoClosePrice, now, now,
                ExperienceGiftRecordStatus.IN_USE, ExperienceGiftRecordStatus.PENDING);
        if (rows == 0) {
            log.warn("体验金激活失败，recordId={} 状态已变更或不存在", recordId);
        }
        return rows > 0;
    }

    // ----------------------------------------------------------------
    // 用户端接口方法（contract-swap-api 使用）
    // ----------------------------------------------------------------

    /**
     * 生效中列表：PENDING + IN_USE，按创建时间倒序
     */
    public List<ExperienceGiftRecord> findActiveByMemberId(Long memberId) {
        return recordRepository.findByMemberIdAndStatusInOrderByCreatedAtDesc(
                memberId,
                Arrays.asList(ExperienceGiftRecordStatus.PENDING, ExperienceGiftRecordStatus.IN_USE));
    }

    /**
     * 已失效列表：USED / EXPIRED / INVALID，按创建时间倒序
     */
    public List<ExperienceGiftRecord> findInactiveByMemberId(Long memberId) {
        return recordRepository.findByMemberIdAndStatusInOrderByCreatedAtDesc(
                memberId,
                Arrays.asList(
                        ExperienceGiftRecordStatus.USED,
                        ExperienceGiftRecordStatus.EXPIRED,
                        ExperienceGiftRecordStatus.INVALID));
    }

    /**
     * 可使用列表（下拉选择）：PENDING 且未过期，按创建时间倒序
     */
    public List<ExperienceGiftRecord> findAvailableByMemberId(Long memberId) {
        return recordRepository.findByMemberIdAndStatusAndExpireAtAfterOrderByCreatedAtDesc(
                memberId, ExperienceGiftRecordStatus.PENDING, new Date());
    }

    /**
     * 可使用列表（按交易对过滤）：PENDING 且未过期，且产品配置支持该交易对
     */
    public List<ExperienceGiftRecord> findAvailableByMemberIdAndCoinSymbol(Long memberId, String coinSymbol) {
        return recordRepository.findAvailableByMemberIdAndCoinSymbol(
                memberId, ExperienceGiftRecordStatus.PENDING, new Date(), coinSymbol);
    }

    /**
     * 查询所有使用中的体验金券（行情监控使用）
     */
    public List<ExperienceGiftRecord> findAllInUse() {
        return recordRepository.findByStatus(ExperienceGiftRecordStatus.IN_USE);
    }

    /**
     * 按交易对查询持仓中的体验金券（撮合引擎 tick 检查使用）
     */
    public List<ExperienceGiftRecord> findInUseByCoinSymbol(String coinSymbol) {
        return recordRepository.findByStatusAndCoinSymbol(ExperienceGiftRecordStatus.IN_USE, coinSymbol);
    }

    /**
     * 更新体验金仓位的止盈止损价格
     *
     * @param recordId       体验金券 ID
     * @param memberId       用户 ID（安全校验）
     * @param stopProfitPrice 止盈触发价，0 或 null 表示取消
     * @param stopLossPrice   止损触发价，0 或 null 表示取消
     * @return 更新成功返回 true
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStopPrices(Long recordId, Long memberId,
                                    BigDecimal stopProfitPrice, BigDecimal stopLossPrice) {
        BigDecimal sp = (stopProfitPrice == null) ? BigDecimal.ZERO : stopProfitPrice;
        BigDecimal sl = (stopLossPrice == null) ? BigDecimal.ZERO : stopLossPrice;
        int rows = recordRepository.updateStopPrices(recordId, memberId, sp, sl,
                new Date(), ExperienceGiftRecordStatus.IN_USE);
        if (rows > 0) {
            // 同步内存缓存，供定时任务直接读取
            experienceGiftRecordCache.updateStopPrices(recordId, sp, sl);
        }
        return rows > 0;
    }

    /**
     * 将已激活的体验金券写入内存缓存（由开仓事务内调用）
     *
     * @param record 已激活（IN_USE）的体验金券
     */
    public void putToCache(ExperienceGiftRecord record) {
        experienceGiftRecordCache.put(record);
    }

    /**
     * 从内存缓存中移除体验金券（由平仓方法调用）
     *
     * @param recordId 体验金券 ID
     */
    public void removeFromCache(Long recordId) {
        experienceGiftRecordCache.remove(recordId);
    }

    /**
     * 返回持仓中体验金券的内存缓存（供行情 tick 遍历，不做 DB 查询）
     *
     * @return 所有持仓中体验金券的只读视图
     */
    public Map<Long, ExperienceGiftRecord> getInUseCache() {
        return experienceGiftRecordCache.getAll();
    }

    /**
     * 标记指定体验金券已触发平仓（用于重复发送过滤）
     *
     * @param recordId 体验金券 ID
     */
    public void markProcessing(Long recordId) {
        experienceGiftRecordCache.markProcessing(recordId);
    }

    /**
     * 判断指定体验金券是否已在处理中（已标记且尚未过期）
     *
     * @param recordId 体验金券 ID
     * @return true 表示应跳过本次触发
     */
    public boolean isProcessing(Long recordId) {
        return experienceGiftRecordCache.isProcessing(recordId);
    }

    /**
     * 清理超过 1 分钟的处理中标记（每次行情 tick 检查完毕后调用）
     */
    public void cleanExpiredProcessing() {
        experienceGiftRecordCache.cleanExpiredProcessing();
    }

    /**
     * 平仓后更新体验金券状态（行情触发/爆仓/到期）
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean closeRecord(Long recordId, BigDecimal pnl, BigDecimal closeFee,
                               ExperienceGiftRecordStatus status,
                               ExperienceGiftCloseReason reason) {
        ExperienceGiftRecord record = dao.findOne(recordId);
        if (record == null) {
            return false;
        }
        if (record.getStatus() != ExperienceGiftRecordStatus.IN_USE) {
            return false;
        }
        Date now = new Date();
        record.setStatus(status);
        record.setPnl(pnl);
        record.setCloseFee(closeFee);
        record.setCloseReason(reason);
        record.setClosedAt(now);
        record.setUpdatedAt(now);
        dao.save(record);
        return true;
    }

    /**
     * 用户体验金汇总统计
     * - totalReceived:    累计领取体验金面额（USDT）—— wallet.totalReceived
     * - cumulativeProfit: 体验金累计盈利（只增）—— wallet.totalProfit
     * - withdrawable:     待提现金额（扣除冻结/已提现后的实际可提金额）—— wallet.pendingAmount
     */
    public Map<String, Object> memberStats(Long memberId) {
        Optional<ExperienceGiftWallet> walletOpt = experienceGiftWalletDao.findByMemberId(memberId);

        BigDecimal totalReceived = BigDecimal.ZERO;
        BigDecimal cumulativeProfit = BigDecimal.ZERO;
        BigDecimal withdrawable = BigDecimal.ZERO;

        if (walletOpt.isPresent()) {
            ExperienceGiftWallet wallet = walletOpt.get();
            totalReceived    = wallet.getTotalReceived();
            cumulativeProfit = wallet.getTotalProfit();
            withdrawable     = wallet.getPendingAmount();
        }

        Map<String, Object> result = new HashMap<>();
        result.put("totalReceived", totalReceived);
        result.put("cumulativeProfit", cumulativeProfit);
        result.put("withdrawable", withdrawable);
        return result;
    }

    // ----------------------------------------------------------------
    // 管理后台方法（admin 使用）
    // ----------------------------------------------------------------

    /**
     * 分页查询体验金列表
     */
    public Page<Map> pageQuery(Long id, Long memberId, Long productId, String productName,
                                Integer statusGroup, Integer grantType,
                                String grantStartTime, String grantEndTime,
                                String expireStartTime, String expireEndTime,
                                int pageNo, int pageSize) {
        return dao.pageQuery(id, memberId, productId, productName, statusGroup, grantType,
                grantStartTime, grantEndTime, expireStartTime, expireEndTime, pageNo, pageSize);
    }

    /**
     * 统计数据（体验金总额、平仓盈利、平仓亏损）
     */
    public Map<String, Object> stats(Long id, Long memberId, Long productId, String productName,
                                     Integer statusGroup, Integer grantType,
                                     String grantStartTime, String grantEndTime,
                                     String expireStartTime, String expireEndTime) {
        return dao.stats(id, memberId, productId, productName, statusGroup, grantType,
                grantStartTime, grantEndTime, expireStartTime, expireEndTime);
    }

    /**
     * 手动回收（仅限未使用状态）
     */
    @Transactional(rollbackFor = Exception.class)
    public String recycle(Long id, Long operatorId) {
        ExperienceGiftRecord record = dao.findOne(id);
        if (record == null) {
            return "体验金记录不存在";
        }
        if (record.getStatus() != ExperienceGiftRecordStatus.PENDING) {
            String statusDesc = record.getStatus().getDescription();
            return "回收失败，当前状态【" + statusDesc + "】不可回收，仅限【未使用】状态";
        }
        record.setStatus(ExperienceGiftRecordStatus.INVALID);
        record.setCloseReason(ExperienceGiftCloseReason.ADMIN_RECYCLE);
        record.setClosedAt(new Date());
        record.setUpdatedAt(new Date());
        dao.save(record);
        return null; // null 表示成功
    }

    /**
     * 查询单条
     */
    public ExperienceGiftRecord findById(Long id) {
        return dao.findOne(id);
    }

    // ----------------------------------------------------------------
    // 管理后台 - 手动赠送体验金
    // ----------------------------------------------------------------

    /**
     * 批量手动赠送体验金
     *
     * @param productId  产品ID（必须是 ENABLED + MANUAL 类型）
     * @param memberIds  目标用户ID列表
     * @param remark     派发备注
     * @param operatorId 操作管理员ID
     * @return 实际赠送成功的张数
     */
    @Transactional(rollbackFor = Exception.class)
    public int grantManual(Long productId, List<Long> memberIds, String remark, Long operatorId) {
        ExperienceGiftProduct product = productRepository.findOne(productId);
        if (product == null || product.getStatus() != ExperienceGiftProductStatus.ENABLED) {
            throw new RuntimeException("产品不存在或已关闭");
        }
        if (product.getGrantType() != ExperienceGiftGrantType.MANUAL) {
            throw new RuntimeException("仅支持手动赠送类型的产品");
        }

        // 从产品交易对取杠杆快照（所有交易对共用同一杠杆值）
        List<ExperienceGiftProductCoin> coins = productCoinRepository.findByProductId(productId);
        int leverage = coins.isEmpty() ? 2 : coins.get(0).getLeverage();

        Date now = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(now);
        cal.add(Calendar.DAY_OF_MONTH, product.getValidityDays());
        Date expireAt = cal.getTime();

        int count = 0;
        for (Long memberId : memberIds) {
            // 检查发放总量上限（0 = 不限）
            if (product.getTotalQuantity() > 0
                    && product.getIssuedCount() + count >= product.getTotalQuantity()) {
                log.warn("手动赠送体验金：产品[{}]已达总量上限({})，memberId={} 跳过",
                        productId, product.getTotalQuantity(), memberId);
                continue;
            }

            // 检查单人领取上限
            long alreadyReceived = recordRepository.countByMemberIdAndProductId(memberId, productId);
            if (alreadyReceived >= product.getPerUserLimit()) {
                log.warn("手动赠送体验金：memberId={} 已领取 {} 张产品[{}]，超出单人上限({})，跳过",
                        memberId, alreadyReceived, productId, product.getPerUserLimit());
                continue;
            }

            ExperienceGiftRecord record = new ExperienceGiftRecord();
            record.setProductId(productId);
            record.setMemberId(memberId);
            record.setProductName(product.getName());
            record.setAmount(product.getAmount());
            record.setMaxProfit(product.getMaxProfitPerCard());
            record.setLeverage(leverage);
            record.setValidityDays(product.getValidityDays());
            record.setGrantType(ExperienceGiftGrantType.MANUAL);
            record.setStatus(ExperienceGiftRecordStatus.PENDING);
            record.setExpireAt(expireAt);
            record.setOperatorId(operatorId);
            record.setOperatorRemark(remark);
            record.setCreatedAt(now);
            record.setUpdatedAt(now);
            dao.save(record);

            // 更新或初始化体验金钱包：total_received += amount
            experienceGiftWalletDao.addReceived(memberId, product.getAmount());

            count++;
        }

        // 累加已发放张数
        if (count > 0) {
            product.setIssuedCount(product.getIssuedCount() + count);
            product.setUpdatedAt(now);
            productRepository.save(product);
        }

        return count;
    }

    // ----------------------------------------------------------------
    // 系统触发 - 充值后异步发放注册体验金
    // ----------------------------------------------------------------

    /**
     * 充值完成后异步触发注册体验金发放，不阻塞充值流程
     *
     * @param memberId         充值用户ID
     * @param rechargeAmount   本次充值金额（USDT）
     * @param registrationTime 用户注册时间
     * @param clientIp         用户登录 IP（用于同 IP 多账户防刷）
     */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void grantRegisterTrigger(Long memberId, BigDecimal rechargeAmount, Date registrationTime, String clientIp) {
        Date now = new Date();

        // 查询所有启用的系统触发注册类型产品
        List<ExperienceGiftProduct> products = productRepository.findByStatusAndGrantTypeAndTriggerType(
                ExperienceGiftProductStatus.ENABLED,
                ExperienceGiftGrantType.SYSTEM_TRIGGER,
                ExperienceGiftTriggerType.REGISTER
        );

        for (ExperienceGiftProduct product : products) {
            try {
                // 1. 检查活动开始时间（活动未开始则跳过，无结束时间限制）
                if (product.getActivityStartTime() != null && now.before(product.getActivityStartTime())) continue;

                // 2. 用户注册时间必须在活动开始时间之后（活动期内注册的新用户才有资格）
                if (product.getActivityStartTime() != null
                        && registrationTime != null
                        && registrationTime.before(product.getActivityStartTime())) continue;

                // 3. 充值金额满足最小充值要求
                if (product.getMinRechargeAmount() != null
                        && rechargeAmount.compareTo(product.getMinRechargeAmount()) < 0) continue;

                // 4. 检查发放总量上限（0 = 不限）
                if (product.getTotalQuantity() > 0 && product.getIssuedCount() >= product.getTotalQuantity()) continue;

                // 5. 检查单人领取上限
                long alreadyReceived = recordRepository.countByMemberIdAndProductId(memberId, product.getId());
                if (alreadyReceived >= product.getPerUserLimit()) continue;

                // 6. 发放体验金
                List<ExperienceGiftProductCoin> coins = productCoinRepository.findByProductId(product.getId());
                int leverage = coins.isEmpty() ? 2 : coins.get(0).getLeverage();

                Calendar cal = Calendar.getInstance();
                cal.setTime(now);
                cal.add(Calendar.DAY_OF_MONTH, product.getValidityDays());
                Date expireAt = cal.getTime();

                ExperienceGiftRecord record = new ExperienceGiftRecord();
                record.setProductId(product.getId());
                record.setMemberId(memberId);
                record.setProductName(product.getName());
                record.setAmount(product.getAmount());
                record.setMaxProfit(product.getMaxProfitPerCard());
                record.setLeverage(leverage);
                record.setValidityDays(product.getValidityDays());
                record.setGrantType(ExperienceGiftGrantType.SYSTEM_TRIGGER);
                record.setStatus(ExperienceGiftRecordStatus.PENDING);
                record.setExpireAt(expireAt);
                record.setClientIp(clientIp);
                record.setCreatedAt(now);
                record.setUpdatedAt(now);
                dao.save(record);

                // 更新体验金钱包
                experienceGiftWalletDao.addReceived(memberId, product.getAmount());

                // 累加已发放张数
                product.setIssuedCount(product.getIssuedCount() + 1);
                product.setUpdatedAt(now);
                productRepository.save(product);

                sendExperienceGiftGrantEmail(memberId, product.getAmount());
                log.info("自动发放注册体验金成功: memberId={}, productId={}, amount={}", memberId, product.getId(), product.getAmount());
            } catch (Exception e) {
                log.error("自动发放注册体验金失败: memberId={}, productId={}", memberId, product.getId(), e);
            }
        }
    }

    /**
     * 体验金发放后发送邮件通知（有邮箱才发送）
     */
    private void sendExperienceGiftGrantEmail(Long memberId, BigDecimal amount) {
        try {
            Member member = memberService.findOne(memberId);
            if (member == null || StringUtils.isBlank(member.getEmail())) {
                return;
            }
            String amountText = amount == null ? "0" : amount.stripTrailingZeros().toPlainString();
            String subject = "【CionVision】体验金发放通知 | " + amountText + "U 体验金已入账";
            String html = buildExperienceGiftGrantHtml(amountText);
            emailProvider.sendSingleSendMail(member.getEmail(), subject, html);
        } catch (Exception e) {
            log.warn("体验金发放邮件发送失败: memberId={}, amount={}", memberId, amount, e);
        }
    }

    private String buildExperienceGiftGrantHtml(String amountText) throws IOException, TemplateException {
        Map<String, Object> model = new HashMap<>(4);
        model.put("amountText", amountText);
        model.put("projectName", sysConfigService.get().getProjectName());
        model.put("projectLogo", sysConfigService.get().getProjectLogo());
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_26);
        cfg.setClassForTemplateLoading(this.getClass(), "/templates");
        cfg.setDefaultEncoding("UTF-8");
        Template template = cfg.getTemplate(EXPERIENCE_GIFT_GRANT_TEMPLATE, "UTF-8");
        return FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
    }
}
