package edu.whut.config;

import edu.whut.types.annotations.DCCValue;
import edu.whut.types.common.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * Dynamic Config Center（DCC）—— 基于 Redis（Redisson）实现的轻量级配置中心。
 *   作用：
 *   1. Bean 初始化阶段扫描带 {@link DCCValue} 注解的字段，将默认值写入 Redis 并注入到 Bean；
 *   2. 通过 Redis Topic 监听配置变更事件，实现运行期热更新；
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class DCCValueBeanFactory implements BeanPostProcessor {

    /** Redis 中所有 DCC Key 的统一前缀 */
    private static final String BASE_CONFIG_PATH = "group_buy_market_dcc_";

    private final RedissonClient redissonClient;

    /** 记录「配置 Key → 注入了该 Key 的 Bean 实例」的映射，用于收到变更后反射刷新字段值 */
    private final Map<String, Object> dccObjGroup = new HashMap<>();

    /**
     * 定义一个 {@link RTopic} Bean 并注册监听器，用于接收
     * “其他节点发布的配置变更消息”。
     * 消息格式：attribute:value，例如 cutRange:30。
     * @param redissonClient 容器注入的 RedissonClient
     * @return 配置变更 Topic 对象（Bean 名称为 dccTopic
     */
    @Bean("dccTopic")
    public RTopic dccRedisTopicListener(RedissonClient redissonClient) {
        RTopic topic = redissonClient.getTopic("group_buy_market_dcc"); //只要有任何客户端往 group_buy_market_dcc 发布消息，这个监听回调就会被触发

        topic.addListener(String.class, (channel, message) -> {
            // message 示例： "cutRange:30"
            String[] split = message.split(Constants.SPLIT);
            String attribute = split[0];            // 字段名
            String value     = split[1];            // 新值

            String key = BASE_CONFIG_PATH + attribute;

            // 1. 写回 Redis，保证一致性
            RBucket<String> bucket = redissonClient.getBucket(key);
            if (!bucket.isExists()) { return; }
            bucket.set(value);

            // 2. 本地内存刷新：反射写回所有注入了该字段的 Bean
            Object objBean = dccObjGroup.get(key);
            if (objBean == null) { return; }

            Class<?> objBeanClass = objBean.getClass();
            // 兼容 AOP 代理场景：取真实目标类
            if (AopUtils.isAopProxy(objBean)) {
                objBeanClass = AopUtils.getTargetClass(objBean);
            }

            try {
                Field field = objBeanClass.getDeclaredField(attribute);
                field.setAccessible(true);
                field.set(objBean, value);
                field.setAccessible(false);

                log.info("DCC → 热更新成功：{} = {}", key, value);
            } catch (Exception e) {
                throw new RuntimeException("DCC 反射刷新失败：" + key, e);
            }
        });

        return topic;
    }

    /**
     * Spring 容器在启动并创建每个 Bean 的时候，都会依次回调postProcessAfterInitialization 方法：
     * 扫描所有带 {@link DCCValue} 注解的字段；
     * 把默认值写入 Redis（若不存在）；
     * 把 Redis 中最新值注入到字段；
     * 记录「key → Bean 实例」映射以便后续热更新。
     *
     * @param bean     当前实例化完成的 Bean
     * @param beanName Spring 为该 Bean 生成的名称
     * @return 可能被改写后的 Bean（此处直接返回原 Bean）
     * @throws BeansException Spring 容器异常
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 处理 AOP 代理：拿到目标类 & 真实对象
        //默认：假设 bean 就是我们想要的目标类
        Class<?> targetBeanClass = bean.getClass();
        Object   targetBeanObj   = bean;
        //如果它是一个 AOP 代理对象，就“剥掉代理”，找到真实的类和实例
        if (AopUtils.isAopProxy(bean)) {
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetBeanObj   = AopProxyUtils.getSingletonTarget(bean);
        }

        // 遍历字段
        for (Field field : targetBeanClass.getDeclaredFields()) {
            //寻找带 @DCCValue 的字段
            if (!field.isAnnotationPresent(DCCValue.class)) { continue; }

            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String rawValue   = dccValue.value();             // eg:"downgradeSwitch:0"
            if (StringUtils.isBlank(rawValue)) {
                throw new RuntimeException(field.getName() + " 缺少 @DCCValue 默认值");
            }

            String[] splits      = rawValue.split(":");
            String   key         = BASE_CONFIG_PATH + splits[0];
            String   defaultVal  = splits.length == 2 ? splits[1] : null;
            if (StringUtils.isBlank(defaultVal)) {
                throw new RuntimeException("DCC Key " + key + " 未配置默认值");
            }

            // 1. Redis 同步：若不存在则写默认值；若已存在则取最新值
            RBucket<String> bucket = redissonClient.getBucket(key);
            String injectedValue = bucket.isExists() ? bucket.get() : defaultVal;
            // 如果 Redis 里本来没有这个 key，就把默认值写进去。
            if (!bucket.isExists()) {
                bucket.set(defaultVal);
            }
            // 2. 反射注入字段
            try {
                field.setAccessible(true);
                field.set(targetBeanObj, injectedValue);
                field.setAccessible(false);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("DCC 注入字段失败：" + key, e);
            }

            // 3. 记录「key → Bean」，供 Topic 监听时热更新
            dccObjGroup.put(key, targetBeanObj);
        }

        return bean;
    }

}
