package com.tzld.piaoquan.ad.engine.commons.helper;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
@Component
public class CreativeKFinalDataHelper {

    @Resource(name = "newAdRedisTemplate")
    private RedisTemplate<String, String> newAdRedisTemplate;

    private static final String CREATIVE_CODE_SET_KEY_FORMAT = "ad:addv:cost:current:creative:code:%s";
    private static final String CREATIVE_K_KEY_FORMAT = "ad:addv:cost:k:%s";

    /**
     * Apollo 动态配置：kFlow 转换参数
     * 配置格式：{"kFlowMax": 1, "kFlowMin": 0.95, "kMin": 0.8}
     */
    @ApolloJsonValue("${ad.addv.kfinal.flow.config:{}}")
    private KFlowConfig kFlowConfig;

    private volatile static Map<String, Double> creativeKFinalMap = Collections.emptyMap();

    @Data
    static class KFlowConfig {
        private double kFlowMax = 1.0;
        private double kFlowMin = 0.95;
        private double kMin = 0.8;
    }

    // 服务启动时初始化数据
    @PostConstruct
    public void init() {
        Map<String, Double> map = updateCreativeKFinalMap();
        creativeKFinalMap = Collections.unmodifiableMap(map);
    }

    // 每1分钟更新一次数据
    @Scheduled(fixedRate = 60 * 1000)
    public void scheduledUpdate() {
        Map<String, Double> map = updateCreativeKFinalMap();
        creativeKFinalMap = Collections.unmodifiableMap(map);
    }

    public static Double getKFinal(String creativeCode) {
        if (creativeCode != null && creativeKFinalMap != null) {
            return creativeKFinalMap.get(creativeCode);
        }
        return null;
    }

    private Map<String, Double> updateCreativeKFinalMap() {
        Map<String, Double> tmpMap = new HashMap<>();
        try {
            String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
            String setKey = String.format(CREATIVE_CODE_SET_KEY_FORMAT, today);
            Set<String> creativeCodes = newAdRedisTemplate.opsForSet().members(setKey);
            if (CollectionUtils.isEmpty(creativeCodes)) {
                log.info("creative kFinal: code set is empty for date={}", today);
                return tmpMap;
            }
            for (String creativeCode : creativeCodes) {
                try {
                    String kKey = String.format(CREATIVE_K_KEY_FORMAT, creativeCode);
                    String value = newAdRedisTemplate.opsForValue().get(kKey);
                    if (StringUtils.isNotBlank(value)) {
                        JSONObject jsonObject = JSON.parseObject(value);
                        Double kFinal = jsonObject.getDouble("kFinal");
                        if (kFinal != null) {
                            double kFlow = computeKFlow(kFinal);
                            tmpMap.put(creativeCode, kFlow);
                        }
                    }
                } catch (Exception e) {
                    log.error("update creative kFinal error creativeCode={}", creativeCode, e);
                }
            }
            log.info("update creative kFinal map success size={}", tmpMap.size());
        } catch (Exception e) {
            log.error("update creative kFinal map error", e);
        }
        return tmpMap;
    }

    /**
     * 根据 kFinal 计算 kFlow：
     * 1. kFlow = kFlowMin + (kFinal - kMin) / (1 - kMin) * (kFlowMax - kFlowMin)
     * 2. kFlow = clamp(kFlow, kFlowMin, kFlowMax)
     */
    private double computeKFlow(double kFinal) {
        double kFlowMax = kFlowConfig != null ? kFlowConfig.getKFlowMax() : 1.0;
        double kFlowMin = kFlowConfig != null ? kFlowConfig.getKFlowMin() : 0.95;
        double kMin = kFlowConfig != null ? kFlowConfig.getKMin() : 0.8;
        if(kMin >=1){
            return 1d;
        }
        double kFlow = kFlowMin + (kFinal - kMin) / (1.0 - kMin) * (kFlowMax - kFlowMin);
        return Math.max(kFlowMin, Math.min(kFlowMax, kFlow));
    }
}
