package ru.yandex.webmaster3.storage.events.service;

import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;

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 ru.yandex.webmaster3.storage.events.data.WMCEvent;

/**
 * ishalaru
 * 15.12.2020
 **/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class WMCEventsObservingService {
    private static final String SOLOMON_RESULT_LABEL = "lb_observer_result";
    private static final String SOLOMON_OBSERVER_LABEL = "lb_observer";

    private final SolomonMetricRegistry solomonMetricRegistry;
    @Qualifier("workerActionMetricsConfiguration")
    private final SolomonTimerConfiguration solomonTimerConfiguration;
    private Map<String, Metrics> metricMap = new ConcurrentHashMap<>();
    @Value("${webmaster3.worker.observing.retries.count:5}")
    private int retriesCount;

    public ObserverResult processEvent(String observerName, WMCEvent event, WMCEventsObserver wmcEventsObserver) {
        ObserverResult result = ObserverResult.FAIL;
        long startAt = System.currentTimeMillis();
        try {
            for (int i = 0; i <= retriesCount; i++) {
                try {
                    log.info("Observer " + observerName + " processing message " + event + ". Attempt #" + i);
                    boolean isProcessedByObserver = wmcEventsObserver.observe(event);
                    result = i == 0 ? ObserverResult.SUCCESS : ObserverResult.RETRY;
                    log.info("Observer " + observerName + " processed message " + event + ". Attempt #" + i);
                    break;
                } catch (MissingDataException e) {
                    String message = "Observer " + observerName +
                            " failed to process event " + event + ". Retries left: " + (retriesCount - i);
                    log.error(message, e);
                    result = ObserverResult.FAIL;
                    break;
                } catch (RuntimeException e) {
                    String message = "Observer " + observerName +
                            " failed to process event " + event + ". Retries left: " + (retriesCount - i);
                    log.error(message);
                    result = ObserverResult.FAIL;
                }
            }
        } finally {
            Metrics metrics = metricMap.computeIfAbsent(observerName, e -> new Metrics(e));
            metrics.result2Timer.get(result).update(Duration.millis(System.currentTimeMillis() - startAt));
        }


        return result;
    }

    private class Metrics {
        private final Map<ObserverResult, SolomonTimer> result2Timer;

        public Metrics(String observerName) {
            SolomonKey baseKey = SolomonKey.create(SOLOMON_OBSERVER_LABEL, observerName);
            this.result2Timer = new EnumMap<>(ObserverResult.class);
            for (ObserverResult result : ObserverResult.values()) {
                result2Timer.put(result, solomonMetricRegistry.createTimer(solomonTimerConfiguration, baseKey.withLabel(SOLOMON_RESULT_LABEL, result.name())));
            }
        }
    }

}
