package com.atlassian.bitbucket.mesh.repair;

import com.atlassian.bitbucket.mesh.repository.ReplicaState;
import com.atlassian.bitbucket.mesh.repository.ReplicaStateRegistry;
import com.atlassian.util.profiling.Ticker;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.binder.MeterBinder;

import javax.annotation.Nonnull;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

import static com.atlassian.bitbucket.mesh.metrics.MetricsConstants.PREFIX_OPERATION;
import static com.atlassian.bitbucket.mesh.metrics.MetricsConstants.PREFIX_REPAIR;

public class DefaultRepairMetrics implements MeterBinder, RepairMetrics {

    private final LongAdder failed;
    private final ReplicaStateRegistry replicaStateRegistry;
    private final AtomicLong running;
    private final LongAdder successful;
    private final LongAdder total;

    private Timer compareReflogsTimer;
    private Timer compareRefsTimer;
    private Timer fetchObjectsTimer;
    private Timer totalTimer;

    public DefaultRepairMetrics(ReplicaStateRegistry replicaStateRegistry) {
        this.replicaStateRegistry = replicaStateRegistry;
        failed = new LongAdder();
        running = new AtomicLong();
        successful = new LongAdder();
        total = new LongAdder();
    }

    @Override
    public void bindTo(@Nonnull MeterRegistry registry) {
        //Repair call counters
        Gauge.builder(PREFIX_REPAIR + "calls.failed", failed::doubleValue)
                .description("The number of failed repair operations")
                .register(registry);
        Gauge.builder(PREFIX_REPAIR + "calls.running", running::doubleValue)
                .description("The number of running repair operations")
                .register(registry);
        Gauge.builder(PREFIX_REPAIR + "calls.successful", successful::doubleValue)
                .description("The number of successful repair operations")
                .register(registry);
        Gauge.builder(PREFIX_REPAIR + "calls.total", total::doubleValue)
                .description("The total number of repair operations")
                .register(registry);
        Gauge.builder(PREFIX_REPAIR + "inconsistentReplicas",
                () -> replicaStateRegistry.countByState(ReplicaState.INCONSISTENT))
                .description("The number of INCONSISTENT repository replicas")
                .register(registry);

        //Repair timers
        String timerPrefix = PREFIX_OPERATION + "repair.";
        compareReflogsTimer = Timer.builder(timerPrefix + "CompareReflogs")
                .description("Duration of the reflog comparison phase of repair")
                .register(registry);
        compareRefsTimer = Timer.builder(timerPrefix + "CompareRefs")
                .description("Duration of the ref comparison phase of repair")
                .register(registry);
        fetchObjectsTimer = Timer.builder(timerPrefix + "FetchObjects")
                .description("Duration of the fetch objects phase of repair")
                .register(registry);
        totalTimer = Timer.builder(timerPrefix + "Total")
                .description("Repair execution statistics")
                .register(registry);
    }

    @Override
    public long getFailed() {
        return failed.longValue();
    }

    @Override
    public long getRunning() {
        return running.get();
    }

    @Override
    public long getSuccessful() {
        return successful.longValue();
    }

    @Override
    public long getTotal() {
        return total.longValue();
    }

    @Nonnull
    @Override
    public CallTracer startTrace() {
        return new SimpleCallTrace();
    }

    private class SimpleCallTrace implements CallTracer {

        private final Timer.Sample sample;

        SimpleCallTrace() {
            running.incrementAndGet();
            total.increment();
            sample = Timer.start();
        }

        @Nonnull
        @Override
        public Ticker compareReflogs() {
            return ticker(compareReflogsTimer);
        }

        @Nonnull
        @Override
        public Ticker compareRefs() {
            return ticker(compareRefsTimer);
        }

        @Nonnull
        @Override
        public Ticker fetchObjects() {
            return ticker(fetchObjectsTimer);
        }

        @Override
        public void stop(boolean success) {
            if (totalTimer != null) {
                sample.stop(totalTimer);
            }
            running.decrementAndGet();
            if (success) {
                successful.increment();
            } else {
                failed.increment();
            }
        }

        private Ticker ticker(Timer timer) {
            Timer.Sample sample = Timer.start();
            return () -> sample.stop(timer);
        }
    }
}
