package com.bizzan.bitrade.util;

import com.bizzan.bitrade.dao.ExperienceGiftRecordRepository;
import com.bizzan.bitrade.entity.ExperienceGiftRecord;
import com.bizzan.bitrade.entity.ExperienceGiftRecordStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 持仓中体验金券的内存缓存工具
 * <p>
 * 缓存的生命周期管理：
 * - 启动：{@link #init()} 从数据库加载所有 IN_USE 状态的券
 * - 开仓成功：{@link #put(ExperienceGiftRecord)} 写入
 * - 止盈止损更新：{@link #updateStopPrices(Long, BigDecimal, BigDecimal)} 同步字段
 * - 平仓成功：{@link #remove(Long)} 移除
 * <p>
 * 行情 tick 通过 {@link #getAll()} 读取缓存，不再每次查询数据库。
 * 重复发送过滤通过 {@link #markProcessing(Long)} / {@link #isProcessing(Long)} 实现，
 * 每次 tick 结束后调用 {@link #cleanExpiredProcessing()} 清理 1 分钟前的记录。
 */
@Slf4j
@Component
public class ExperienceGiftRecordCache {

    /** 处理中记录的过期时间（毫秒） */
    private static final long PROCESSING_EXPIRE_MS = 60_000L;

    @Autowired
    private ExperienceGiftRecordRepository recordRepository;

    /**
     * 内存缓存：key=体验金券ID，value=持仓中的体验金券记录
     */
    private final Map<Long, ExperienceGiftRecord> cache = new ConcurrentHashMap<>();

    /**
     * 重复发送过滤表：key=体验金券ID，value=首次触发时间戳（毫秒）
     * <p>
     * 用于防止同一条记录在多个连续 tick 中重复发送 Kafka 平仓消息。
     * 条目在 {@link #cleanExpiredProcessing()} 中清理（距记录时间超过 {@link #PROCESSING_EXPIRE_MS}）。
     */
    private final Map<Long, Long> processingIds = new ConcurrentHashMap<>();

    /**
     * 启动时从数据库加载所有持仓中的体验金券
     */
    @PostConstruct
    public void init() {
        // 加载所有 IN_USE 状态的体验金券
        List<ExperienceGiftRecord> list = recordRepository.findByStatus(ExperienceGiftRecordStatus.IN_USE);
        if (list != null) {
            for (ExperienceGiftRecord r : list) {
                cache.put(r.getId(), r);
            }
        }
        log.info("体验金内存缓存初始化完成，加载 {} 条", cache.size());
    }

    /**
     * 开仓成功后写入缓存
     *
     * @param record 已激活（IN_USE 状态）的体验金券
     */
    public void put(ExperienceGiftRecord record) {
        // 空值保护
        if (record == null || record.getId() == null) {
            return;
        }
        cache.put(record.getId(), record);
    }

    /**
     * 平仓后从缓存移除
     *
     * @param recordId 体验金券 ID
     */
    public void remove(Long recordId) {
        if (recordId == null) {
            return;
        }
        cache.remove(recordId);
    }

    /**
     * 止盈止损价格更新时同步缓存字段
     *
     * @param recordId        体验金券 ID
     * @param stopProfitPrice 新止盈触发价（0 表示取消）
     * @param stopLossPrice   新止损触发价（0 表示取消）
     */
    public void updateStopPrices(Long recordId, BigDecimal stopProfitPrice, BigDecimal stopLossPrice) {
        if (recordId == null) {
            return;
        }
        ExperienceGiftRecord cached = cache.get(recordId);
        if (cached != null) {
            // BigDecimal 不可变，引用赋值对其他线程可见（读操作无副作用）
            cached.setStopProfitPrice(stopProfitPrice);
            cached.setStopLossPrice(stopLossPrice);
        }
    }

    /**
     * 返回所有持仓中体验金券的只读视图（供定时任务遍历）
     *
     * @return 不可修改的缓存视图
     */
    public Map<Long, ExperienceGiftRecord> getAll() {
        return Collections.unmodifiableMap(cache);
    }

    /**
     * 返回缓存中当前条目数量
     */
    public int size() {
        return cache.size();
    }

    /**
     * 标记指定体验金券已触发平仓（防止同一 tick 内或下一 tick 重复发送 Kafka）
     *
     * @param recordId 体验金券 ID
     */
    public void markProcessing(Long recordId) {
        if (recordId == null) {
            return;
        }
        processingIds.put(recordId, System.currentTimeMillis());
    }

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

    /**
     * 清理 {@link #processingIds} 中超过 {@link #PROCESSING_EXPIRE_MS} 的过期条目
     * <p>
     * 每次行情 tick 检查完毕后调用，避免内存持续增长。
     */
    public void cleanExpiredProcessing() {
        long now = System.currentTimeMillis();
        // 移除所有距首次触发时间超过 1 分钟的条目
        processingIds.entrySet().removeIf(entry -> now - entry.getValue() > PROCESSING_EXPIRE_MS);
    }
}
