package com.eactive.eai.common.circuitBreaker;

import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.custom.alarm.AlarmStateManager;
import com.eactive.eai.custom.alarm.condition.AlarmCondition;
import com.eactive.eai.custom.alarm.event.AlarmEvent;
import com.eactive.eai.custom.alarm.key.CoreAlarmKey;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.micrometer.tagged.TaggedCircuitBreakerMetrics;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.apache.commons.lang3.math.NumberUtils;

import java.time.Duration;

public class CircuitBreakerManager implements Lifecycle {

    PrometheusMeterRegistry meterRegistry;
    CircuitBreakerRegistry circuitBreakerRegistry;
//    CircuitBreakerConfig config;

//    Map<String, CircuitBreaker> circuitBreakerMap = new HashMap<>();
    static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
    private static CircuitBreakerManager INSTANCE ;

    private boolean started;


    private LifecycleSupport lifecycle = new LifecycleSupport(this);

    public static synchronized CircuitBreakerManager getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new CircuitBreakerManager();
        }
        return INSTANCE;
    }

    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    public LifecycleListener[] findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    private void init(){
        meterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);

        int failureRateThreshold = NumberUtils.toInt(PropManager.getInstance().getProperty("CircuitBreaker", "failureRateThreshold"), 50);
        int slidingWindowSize = NumberUtils.toInt(PropManager.getInstance().getProperty("CircuitBreaker", "slidingWindowSize"), 100);
        int minimumNumberOfCalls = NumberUtils.toInt(PropManager.getInstance().getProperty("CircuitBreaker", "minimumNumberOfCalls"), 100);
        int waitDurationInOpenState = NumberUtils.toInt(PropManager.getInstance().getProperty("CircuitBreaker", "waitDurationInOpenState"), 60);
        int permittedNumberOfCallsInHalfOpenState = NumberUtils.toInt(PropManager.getInstance().getProperty("CircuitBreaker", "permittedNumberOfCallsInHalfOpenState"), 10);

        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
                .failureRateThreshold(failureRateThreshold)
                .slidingWindowSize(slidingWindowSize)
                .minimumNumberOfCalls(minimumNumberOfCalls)
                .waitDurationInOpenState(Duration.ofSeconds(waitDurationInOpenState))
                .permittedNumberOfCallsInHalfOpenState(permittedNumberOfCallsInHalfOpenState)
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
                .build();

        circuitBreakerRegistry = CircuitBreakerRegistry.of(config);

        TaggedCircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry).bindTo(meterRegistry);
        
        circuitBreakerRegistry.getEventPublisher().onEntryAdded(event -> {
            CircuitBreaker cb = event.getAddedEntry();
            String apiId = cb.getName();

            // 여기서 상태 변경 리스너를 한 번만 등록
            cb.getEventPublisher().onStateTransition(stateEvent -> {
		    CircuitBreaker.State from = stateEvent.getStateTransition().getFromState();
		    CircuitBreaker.State to = stateEvent.getStateTransition().getToState();

		    AlarmEvent alarmEvent = AlarmEvent.builder().key(CoreAlarmKey.CircuitBreaker)
			    .condition(AlarmCondition.builder().build())
			    .message(String.format("CircuitBreaker state changed. apiId=%s, %s -> %s", apiId, from, to))
			    .build();

		    AlarmStateManager.getAlarmStateManager().onEvnet(alarmEvent);

		    logger.warn("CircuitBreaker state changed. apiId={}, {} -> {}", apiId, from, to);
		});
            
            logger.info("Registered StateTransition listener for: {}", apiId);
        });

        logger.info("init CircuitBreaker - "+config);
    }

    public void reload(){
        init();
    }

    public PrometheusMeterRegistry getMeterRegistry(){
        return meterRegistry;
    }

    @Override
    public void start() throws LifecycleException {
        lifecycle.fireLifecycleEvent(STARTING_EVENT, this);

        init();

        started = true;
        lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
    }

    @Override
    public void stop() throws LifecycleException {
        if (!started) {
            throw new LifecycleException("RECEAICRT203");
        }
        lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
        started = false;
        circuitBreakerRegistry = null;
        lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
    }

    @Override
    public boolean isStarted() {
        return started;
    }

    public CircuitBreaker getCircuitBreaker(String apiId){
        return circuitBreakerRegistry.circuitBreaker(apiId);
    }
}
