// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.metrics.proc;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.google.gerrit.common.Version;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.metrics.CallbackMetric0;
import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.FieldOrdering;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;

public class ProcMetricModuleTest {
  @Inject MetricMaker metrics;

  @Inject MetricRegistry registry;

  @Test
  public void constantBuildLabel() {
    Gauge<String> buildLabel = gauge("build/label");
    assertThat(buildLabel.getValue()).isEqualTo(Version.getVersion());
  }

  @Test
  public void procUptime() {
    Gauge<Long> birth = gauge("proc/birth_timestamp");
    assertThat(birth.getValue())
        .isAtMost(TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()));

    Gauge<Long> uptime = gauge("proc/uptime");
    assertThat(uptime.getValue()).isAtLeast(1L);
  }

  @Test
  public void counter0() {
    Counter0 cntr =
        metrics.newCounter("test/count", new Description("simple test").setCumulative());

    Counter raw = get("test/count", Counter.class);
    assertThat(raw.getCount()).isEqualTo(0);

    cntr.increment();
    assertThat(raw.getCount()).isEqualTo(1);

    cntr.incrementBy(5);
    assertThat(raw.getCount()).isEqualTo(6);
  }

  @Test
  public void counter1() {
    Counter1<String> cntr =
        metrics.newCounter(
            "test/count",
            new Description("simple test").setCumulative(),
            Field.ofString("action", Field.ignoreMetadata()).build());

    Counter total = get("test/count_total", Counter.class);
    assertThat(total.getCount()).isEqualTo(0);

    cntr.increment("passed");
    Counter passed = get("test/count/passed", Counter.class);
    assertThat(total.getCount()).isEqualTo(1);
    assertThat(passed.getCount()).isEqualTo(1);

    cntr.incrementBy("failed", 5);
    Counter failed = get("test/count/failed", Counter.class);
    assertThat(total.getCount()).isEqualTo(6);
    assertThat(passed.getCount()).isEqualTo(1);
    assertThat(failed.getCount()).isEqualTo(5);
  }

  @Test
  public void counterPrefixFields() {
    Counter1<String> cntr =
        metrics.newCounter(
            "test/count",
            new Description("simple test")
                .setCumulative()
                .setFieldOrdering(FieldOrdering.PREFIX_FIELDS_BASENAME),
            Field.ofString("action", Field.ignoreMetadata()).build());

    Counter total = get("test/count_total", Counter.class);
    assertThat(total.getCount()).isEqualTo(0);

    cntr.increment("passed");
    Counter passed = get("test/passed/count", Counter.class);
    assertThat(total.getCount()).isEqualTo(1);
    assertThat(passed.getCount()).isEqualTo(1);

    cntr.incrementBy("failed", 5);
    Counter failed = get("test/failed/count", Counter.class);
    assertThat(total.getCount()).isEqualTo(6);
    assertThat(passed.getCount()).isEqualTo(1);
    assertThat(failed.getCount()).isEqualTo(5);
  }

  @Test
  public void callbackMetric0() {
    CallbackMetric0<Long> cntr =
        metrics.newCallbackMetric(
            "test/count", Long.class, new Description("simple test").setCumulative());

    AtomicInteger invocations = new AtomicInteger(0);
    metrics.newTrigger(
        cntr,
        () -> {
          invocations.getAndIncrement();
          cntr.set(42L);
        });

    // Triggers run immediately with DropWizard binding.
    assertThat(invocations.get()).isEqualTo(1);

    Gauge<Long> raw = gauge("test/count");
    assertThat(raw.getValue()).isEqualTo(42);

    // Triggers are debounced to avoid being fired too frequently.
    assertThat(invocations.get()).isEqualTo(1);
  }

  @Test
  public void invalidName1() {
    assertThrows(
        IllegalArgumentException.class,
        () -> metrics.newCounter("invalid name", new Description("fail")));
  }

  @Test
  public void invalidName2() {
    assertThrows(
        IllegalArgumentException.class,
        () -> metrics.newCounter("invalid/ name", new Description("fail")));
  }

  @SuppressWarnings({"unchecked", "cast"})
  private <V> Gauge<V> gauge(String name) {
    return (Gauge<V>) get(name, Gauge.class);
  }

  private <M extends Metric> M get(String name, Class<M> type) {
    Metric m = registry.getMetrics().get(name);
    assertWithMessage(name).that(m).isNotNull();
    assertWithMessage(name).that(m).isInstanceOf(type);

    @SuppressWarnings("unchecked")
    M result = (M) m;
    return result;
  }

  @Before
  public void setup() {
    Injector injector = Guice.createInjector(new DropWizardMetricMaker.ApiModule());

    LifecycleManager mgr = new LifecycleManager();
    mgr.add(injector);
    mgr.start();

    injector.injectMembers(this);
  }
}
