package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"reflect"
	"sort"
	"testing"

	"github.com/gogo/protobuf/proto"

	"github.com/davecgh/go-spew/spew"
	"github.com/go-graphite/carbonmem/mwhisper"
	pb "github.com/go-graphite/carbonzipper/carbonzipperpb"
)

func TestParseTopK(t *testing.T) {

	var tests = []struct {
		query   string
		prefix  string
		seconds int32
		ok      bool
	}{
		// good
		{"prefix.blah.*.TopK.10m", "prefix.blah.*", 10 * 60, true},
		{"prefix.blah.*.TopK.10s", "prefix.blah.*", 10, true},
		{"prefix.blah.foo.TopK.10m", "prefix.blah.foo", 600, true},

		// bad
		{"prefix.foo.bar.baz", "", 0, false},
		{"prefix.blah.TopK.10m*", "", 0, false},
		{"prefix.blah.TopK.10", "", 0, false},
		{"prefix.blah.TopK.10m.", "", 0, false},
		{"prefix.blah.TopK.10z", "", 0, false},
		{"prefix.blah.TopK.10m.*.foo", "", 0, false},
		{"prefix.blah.TopK.10m.foo.*", "", 0, false},
		{"prefix.blah.TopK.10s*", "", 0, false},
		{"prefix.blah.TopK.-10m.*", "", 0, false},
		{"prefix.blah.TopK.*", "", 0, false},
		{"prefix.blah.TopK.", "", 0, false},
	}

	for _, tt := range tests {
		if prefix, seconds, ok := parseTopK(tt.query); prefix != tt.prefix || seconds != tt.seconds || ok != tt.ok {
			t.Errorf("parseTopK(%s)=(%q,%v,%v), want (%q,%v,%v)", tt.query, prefix, seconds, ok, tt.prefix, tt.seconds, tt.ok)
		}
	}
}

func TestTopKFind(t *testing.T) {

	metrics := mwhisper.NewWhisper(120, 60, 600)

	for _, m := range []struct {
		epoch  int32
		metric string
		count  uint64
	}{
		{120, "foo.bar", 10},
		{120, "foo.baz", 50},
		{120 + 1*60, "foo.bar", 11},
		{120 + 1*60, "foo.baz", 40},

		{120 + 2*60, "foo.bar", 12},
		{120 + 3*60, "foo.bar", 13},
		{120 + 3*60, "foo.qux", 13},
		{120 + 4*60, "foo.bar", 14},
	} {
		metrics.Set(m.epoch, m.metric, m.count)
	}

	Whispers.metrics = map[string]*mwhisper.Whisper{"foo": metrics}

	for _, tt := range []struct {
		query string
		want  pb.GlobResponse
	}{
		{
			"foo.*",
			pb.GlobResponse{
				Name: proto.String("foo.*"),
				Matches: []*pb.GlobMatch{
					&pb.GlobMatch{Path: proto.String("foo.bar"), IsLeaf: proto.Bool(true)},
					&pb.GlobMatch{Path: proto.String("foo.baz"), IsLeaf: proto.Bool(true)},
					&pb.GlobMatch{Path: proto.String("foo.qux"), IsLeaf: proto.Bool(true)},
				},
			},
		},

		{
			"foo.*.TopK.3m",
			pb.GlobResponse{
				Name: proto.String("foo.*.TopK.3m"),
				Matches: []*pb.GlobMatch{
					&pb.GlobMatch{Path: proto.String("foo.bar.TopK.3m"), IsLeaf: proto.Bool(true)},
					&pb.GlobMatch{Path: proto.String("foo.qux.TopK.3m"), IsLeaf: proto.Bool(true)},
				},
			},
		},

		{
			"foo.*.TopK.5m",
			pb.GlobResponse{
				Name: proto.String("foo.*.TopK.5m"),
				Matches: []*pb.GlobMatch{
					&pb.GlobMatch{Path: proto.String("foo.baz.TopK.5m"), IsLeaf: proto.Bool(true)},
					&pb.GlobMatch{Path: proto.String("foo.bar.TopK.5m"), IsLeaf: proto.Bool(true)},
					&pb.GlobMatch{Path: proto.String("foo.qux.TopK.5m"), IsLeaf: proto.Bool(true)},
				},
			},
		},
	} {
		req, _ := http.NewRequest("GET", fmt.Sprintf("/metrics/find/?query=%s&format=json", tt.query), nil)

		w := httptest.NewRecorder()

		findHandler(w, req)

		var response pb.GlobResponse

		json.Unmarshal(w.Body.Bytes(), &response)

		if !reflect.DeepEqual(response, tt.want) {
			t.Errorf("Find(%s)=%#v, want %#v", tt.query, spew.Sdump(response), spew.Sdump(tt.want))
		}
	}
}

func TestTopKRender(t *testing.T) {

	metrics := mwhisper.NewWhisper(100, 10, 60)

	for _, m := range []struct {
		epoch  int32
		metric string
		count  uint64
	}{
		{120, "foo.bar", 10},
		{120, "foo.baz", 50},
		{121, "foo.bar", 11},
		{121, "foo.baz", 40},

		{122, "foo.bar", 12},
		{123, "foo.bar", 13},
		{123, "foo.qux", 13},
		{124, "foo.bar", 14},
	} {
		metrics.Set(m.epoch, m.metric, m.count)
	}

	Whispers.metrics = map[string]*mwhisper.Whisper{"foo": metrics}

	for _, tt := range []struct {
		target string
		from   int32
		until  int32
		want   pb.MultiFetchResponse
	}{
		{
			"foo.bar",
			120, 124,
			pb.MultiFetchResponse{
				Metrics: []*pb.FetchResponse{
					&pb.FetchResponse{
						Name:      proto.String("foo.bar"),
						StartTime: proto.Int32(120),
						StopTime:  proto.Int32(124),
						StepTime:  proto.Int32(1),
						Values:    []float64{10, 11, 12, 13, 14},
						IsAbsent:  []bool{false, false, false, false, false},
					},
				},
			},
		},

		{
			"foo.bar.TopK.3s",
			120, 124,
			pb.MultiFetchResponse{
				Metrics: []*pb.FetchResponse{
					&pb.FetchResponse{
						Name:      proto.String("foo.bar.TopK.3s"),
						StartTime: proto.Int32(120),
						StopTime:  proto.Int32(124),
						StepTime:  proto.Int32(1),
						Values:    []float64{10, 11, 12, 13, 14},
						IsAbsent:  []bool{false, false, false, false, false},
					},
				},
			},
		},
	} {
		req, _ := http.NewRequest("GET", fmt.Sprintf("/render/?target=%s&from=%d&until=%d&format=json", tt.target, tt.from, tt.until), nil)

		w := httptest.NewRecorder()

		renderHandler(w, req)

		var response pb.MultiFetchResponse

		json.Unmarshal(w.Body.Bytes(), &response)

		if !reflect.DeepEqual(response, tt.want) {
			t.Errorf("Render(%s,%d,%d)=%#v, want %#v", tt.target, tt.from, tt.until, spew.Sdump(response), spew.Sdump(tt.want))
		}
	}
}

func TestNodePrefix(t *testing.T) {

	var tests = []struct {
		metric string
		n      int
		want   string
	}{
		{"foo.bar.baz.qux", 3, "foo.bar.baz"},
	}

	for _, tt := range tests {
		if got := findNodePrefix(tt.n, tt.metric); got != tt.want {
			t.Errorf("findNodePrefix(%d, %q)=%q, want %q", tt.n, tt.metric, got, tt.want)
		}
	}
}

func TestWhisperGlob(t *testing.T) {

	var whispers = whispers{
		metrics: map[string]*mwhisper.Whisper{
			"foo.bar.baz.qux": nil,
			"foo.bar.boz.qux": nil,
			"foo.bar.baz.zot": nil,
		},
	}

	for _, tt := range []struct {
		query string
		want  []string
	}{
		{
			"foo.*",
			[]string{
				"foo.bar",
				"foo.bar",
				"foo.bar",
			},
		},
		{
			"foo.bar.*",
			[]string{
				"foo.bar.baz",
				"foo.bar.baz",
				"foo.bar.boz",
			},
		},
		{
			"foo.bar.b*z.qux",
			[]string{
				"foo.bar.baz.qux",
				"foo.bar.boz.qux",
			},
		},
	} {

		got := whispers.Glob(tt.query)

		var strs []string
		for _, g := range got {
			strs = append(strs, g.Metric)
		}

		sort.Strings(strs)

		if !reflect.DeepEqual(strs, tt.want) {
			t.Errorf("Whispers.Glob(%s)=%v, want %v", tt.query, strs, tt.want)
		}
	}

}