/*
Copyright 2019 The Vitess Authors.

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 stats

import (
	"expvar"
	"testing"
	"time"
)

// For tests, we want to control exactly the time used by Rates.
// The way Rates works is:
// - at creation, do a snapshot.
// - every interval, do a snapshot.
// So in these tests, we make sure to always call snapshot() every interval.
// We do other actions after epsilon, but then wait for intervalMinusEpsilon
// and call snapshot().
const (
	interval             = 1 * time.Second
	epsilon              = 50 * time.Millisecond
	intervalMinusEpsilon = interval - epsilon
)

func TestRates(t *testing.T) {
	now := time.Now()
	timeNow = func() time.Time {
		return now
	}

	clear()
	c := NewCountersWithSingleLabel("rcounter1", "rcounter help", "label")
	r := NewRates("rates1", c, 3, -1*time.Second)
	r.snapshot()
	now = now.Add(epsilon)
	c.Add("tag1", 0)
	c.Add("tag2", 0)
	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)
	checkRates(t, r, "after 1s", 0.0, `{"tag1":[0],"tag2":[0]}`)

	c.Add("tag1", 10)
	c.Add("tag2", 20)
	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)
	checkRates(t, r, "after 2s", 30.0, `{"tag1":[0,10],"tag2":[0,20]}`)

	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)
	checkRates(t, r, "after 3s", 0.0, `{"tag1":[0,10,0],"tag2":[0,20,0]}`)

	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)
	checkRates(t, r, "after 4s", 0.0, `{"tag1":[10,0,0],"tag2":[20,0,0]}`)
}

func checkRates(t *testing.T, r *Rates, desc string, wantRate float64, wantRateMap string) {
	if got := r.String(); got != wantRateMap {
		t.Errorf("%v: want %s, got %s", desc, wantRateMap, got)
	}
	if got := r.TotalRate(); got != wantRate {
		t.Errorf("%v: want rate %v, got rate %v", desc, wantRate, got)
	}
}

func TestRatesConsistency(t *testing.T) {
	now := time.Now()
	timeNow = func() time.Time {
		return now
	}

	// This tests the following invariant: in the time window
	// covered by rates, the sum of the rates reported must be
	// equal to the count reported by the counter.
	clear()
	c := NewCountersWithSingleLabel("rcounter4", "rcounter4 help", "label")
	r := NewRates("rates4", c, 100, -1*time.Second)
	r.snapshot()

	now = now.Add(epsilon)
	c.Add("a", 1000)
	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)
	c.Add("a", 1)
	now = now.Add(intervalMinusEpsilon)
	r.snapshot()
	now = now.Add(epsilon)

	result := r.Get()
	counts := c.Counts()
	t.Logf("r.Get(): %v", result)
	t.Logf("c.Counts(): %v", counts)

	rate, count := result["a"], counts["a"]

	var sum float64
	for _, v := range rate {
		sum += v
	}
	if sum != float64(counts["a"]) {
		t.Errorf("rate inconsistent with count: sum of %v != %v", rate, count)
	}

}

func TestRatesHook(t *testing.T) {
	clear()
	c := NewCountersWithSingleLabel("rcounter2", "rcounter2 help", "label")
	var gotname string
	var gotv *Rates
	clear()
	Register(func(name string, v expvar.Var) {
		gotname = name
		gotv = v.(*Rates)
	})

	v := NewRates("rates2", c, 2, 10*time.Second)
	if gotname != "rates2" {
		t.Errorf("want rates2, got %s", gotname)
	}
	if gotv != v {
		t.Errorf("want %#v, got %#v", v, gotv)
	}
}
