package ru.yandex.webmaster3.core.metrics.externals;

import org.apache.commons.lang3.tuple.Triple;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import ru.yandex.webmaster3.core.solomon.metric.SolomonKey;
import ru.yandex.webmaster3.core.solomon.metric.SolomonMetricRegistry;
import ru.yandex.webmaster3.core.solomon.metric.SolomonTimer;
import ru.yandex.webmaster3.core.solomon.metric.SolomonTimerConfiguration;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by ifilippov5 on 18.06.18.
 */
public class ExternalDependenciesMetricsService implements ApplicationContextAware {
    private static final Logger log = LoggerFactory.getLogger(ExternalDependenciesMetricsService.class);

    protected static final String SOLOMON_LABEL_SERVICE = "external_service";
    protected static final String SOLOMON_LABEL_METHOD = "method";
    protected static final String SOLOMON_LABEL_RESULT = "result";

    private SolomonMetricRegistry solomonMetricRegistry;
    private SolomonTimerConfiguration timerConfiguration;
    private ConcurrentHashMap<Triple<String, String, ExternalAPICallTracker.Status>, SolomonTimer> timers = new ConcurrentHashMap<>();

    private ApplicationContext applicationContext;

    public void init() {
        Map<String, ExternalAPIServiceBase> dependencies = applicationContext.getBeansOfType(ExternalAPIServiceBase.class);
        for (String beanName : dependencies.keySet()) {
            ExternalAPIServiceBase bean = dependencies.get(beanName);
            log.info("Bean {} object {}", beanName, bean);
            String serviceName = bean.getServiceName();
            Method[] methods = bean.getClass().getDeclaredMethods();
            List<String> externallyDependentMethods = new ArrayList<>();
            for (Method method : methods) {
                String methodName = ExternalDependencyUtils.getMethodName(method);
                if (methodName != null) {
                    externallyDependentMethods.add(methodName);
                }
            }
            initializeMetrics(serviceName, externallyDependentMethods);
        }
        ExternalAPICallTracker.subscribe(event -> {
            updateMetrics(event.getServiceName(), event.getMethodName(), event.getStatus(), event.getCallDuration());
        });
    }

    private SolomonTimer createTimer(String service, String method, ExternalAPICallTracker.Status status) {
        return solomonMetricRegistry.createTimer(timerConfiguration,
                SolomonKey.create(SOLOMON_LABEL_SERVICE, service)
                        .withLabel(SOLOMON_LABEL_METHOD, method)
                        .withLabel(SOLOMON_LABEL_RESULT, status.name().toLowerCase())
        );
    }

    private SolomonTimer getCachedTimer(String service, String method, ExternalAPICallTracker.Status status) {
        return timers.computeIfAbsent(Triple.of(service, method, status), igm -> createTimer(service, method, status));
    }

    private void initializeMetrics(String externalServiceName, Collection<String> methods) {
        methods.forEach(m -> {
            for (ExternalAPICallTracker.Status status : ExternalAPICallTracker.Status.values()) {
                getCachedTimer(externalServiceName, m, status);
            }
        });
    }

    private void updateMetrics(String externalServiceName, String methodName, ExternalAPICallTracker.Status status, Duration time) {
        getCachedTimer(externalServiceName, methodName, status).update(time);
    }

    @Required
    public void setSolomonMetricRegistry(SolomonMetricRegistry solomonMetricRegistry) {
        this.solomonMetricRegistry = solomonMetricRegistry;
    }

    @Required
    public void setTimerConfiguration(SolomonTimerConfiguration timerConfiguration) {
        this.timerConfiguration = timerConfiguration;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
