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

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tzld.piaoquan.ad.engine.commons.redis.AlgorithmRedisHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * COPC v3 系数数据缓存。
 */
@Slf4j
@Component
public class CopcV3DataHelper {

    private static final String REDIS_KEY = "ad:engine:strategy:clib:copc_v3";
    private static final long REFRESH_INTERVAL_MILLIS = 5 * 60 * 1000L;
    private static final String EDGE_KEY_PREFIX = "edge:";
    private static final String LANDING_KEY_PREFIX = "landing";
    private static final String BUCKET_KEY_PREFIX = "bucket";
    private static final String EVENT_KEY_PREFIX = "event";
    private static final String GLOBAL_KEY = "global";
    private static final int BUCKET_COUNT = 6;

    @Autowired
    protected AlgorithmRedisHelper algRedisHelper;

    /**
     * Redis value 中同时包含系数和版本等元数据，因此统一保留为字符串。
     */
    private static volatile Map<String, String> copcMap = Collections.emptyMap();

    @PostConstruct
    public void init() {
        refreshCopcMap();
    }

    @Scheduled(fixedRate = REFRESH_INTERVAL_MILLIS)
    public void scheduledUpdate() {
        refreshCopcMap();
    }

    public static String getValue(String key) {
        return key == null ? null : copcMap.get(key);
    }

    public static Map<String, String> getCopcMap() {
        return copcMap;
    }

    /**
     * 按分数所在分桶获取 COPC 加权系数。
     *
     * <p>分桶规则为：{@code score <= edge:1} 属于第 1 桶，依次类推；
     * 大于 {@code edge:5} 的分数属于第 6 桶。查询顺序为
     * {@code landing -> bucket -> event -> global}。</p>
     */
    public static Double getLandingCoefficient(Double score, Integer landingPageType, String targetingConversion) {
        if (!isFinite(score)) {
            return null;
        }

        int bucketNo = getBucketNo(score);
        String event = targetingConversion == null ? null : targetingConversion.trim();
        if (event != null && !event.isEmpty()) {
            Double coefficient;
            if (bucketNo > 0 && landingPageType != null) {
                String landingKey = String.format("%s:%s:%s:%s", LANDING_KEY_PREFIX, event,
                        landingPageType, bucketNo);
                coefficient = getFiniteDoubleValue(landingKey);
                if (coefficient != null) {
                    return coefficient;
                }
            }
            if (bucketNo > 0) {
                String bucketKey = String.format("%s:%s:%s", BUCKET_KEY_PREFIX, event, bucketNo);
                coefficient = getFiniteDoubleValue(bucketKey);
                if (coefficient != null) {
                    return coefficient;
                }
            }
            coefficient = getFiniteDoubleValue(EVENT_KEY_PREFIX + ":" + event);
            if (coefficient != null) {
                return coefficient;
            }
        }
        return getFiniteDoubleValue(GLOBAL_KEY);
    }

    private static int getBucketNo(double score) {
        for (int bucketNo = 1; bucketNo < BUCKET_COUNT; bucketNo++) {
            Double edge = getFiniteDoubleValue(EDGE_KEY_PREFIX + bucketNo);
            if (edge == null) {
                return 0;
            }
            if (score <= edge) {
                return bucketNo;
            }
        }
        return BUCKET_COUNT;
    }

    private static Double getFiniteDoubleValue(String key) {
        String value = getValue(key);
        if (value == null || value.trim().isEmpty()) {
            return null;
        }
        try {
            Double result = Double.valueOf(value);
            return isFinite(result) ? result : null;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private static boolean isFinite(Double value) {
        return value != null && !value.isNaN() && !value.isInfinite();
    }

    private void refreshCopcMap() {
        Map<String, String> map = loadCopcMap();
        if (map == null) {
            return;
        }
        copcMap = Collections.unmodifiableMap(map);
        log.info("update COPC v3 data success size={}", copcMap.size());
    }

    /**
     * @return 解析失败时返回 {@code null}，以保留上一次成功加载的数据。
     */
    private Map<String, String> loadCopcMap() {
        try {
            String value = algRedisHelper.get(REDIS_KEY);
            if (value == null || value.trim().isEmpty()) {
                return Collections.emptyMap();
            }

            JSONObject jsonObject = JSON.parseObject(value);
            if (jsonObject == null) {
                log.error("COPC v3 redis value is not a JSON object");
                return null;
            }

            Map<String, String> map = new HashMap<>(jsonObject.size());
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                if (entry.getValue() != null) {
                    map.put(entry.getKey(), String.valueOf(entry.getValue()));
                }
            }
            return map;
        } catch (Exception e) {
            log.error("update COPC v3 data error", e);
            return null;
        }
    }
}
