package gen

import (
	"fmt"
	"math"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/models"
	"github.com/influxdata/influxdb/v2/tsdb"
)

var (
	org         = influxdb.ID(0xff00ff00)
	bucket      = influxdb.ID(0xcc00cc00)
	orgBucketID = tsdb.EncodeName(org, bucket)
)

func sg(m, prefix, field string, counts ...int) SeriesGenerator {
	spec := TimeSequenceSpec{Count: 1, Start: time.Unix(0, 0), Delta: time.Second}
	ts := NewTimestampSequenceFromSpec(spec)
	vs := NewFloatConstantValuesSequence(1)
	vg := NewTimeFloatValuesSequence(spec.Count, ts, vs)
	return NewSeriesGenerator(orgBucketID, []byte(field), vg, NewTagsValuesSequenceCounts(m, field, prefix, counts))
}

func tags(sb *strings.Builder, m, prefix, f string, vals []int) {
	sb.WriteByte(',')

	// max tag width
	tw := int(math.Ceil(math.Log10(float64(len(vals)))))
	tf := fmt.Sprintf("%s%%0%dd=value%%d", prefix, tw)
	tvs := make([]string, 0, len(vals)+2)

	tvs = append(tvs, fmt.Sprintf("%s=%s", models.MeasurementTagKey, m))

	for i := range vals {
		tvs = append(tvs, fmt.Sprintf(tf, i, vals[i]))
	}

	tvs = append(tvs, fmt.Sprintf("%s=%s", models.FieldKeyTagKey, f))

	sb.WriteString(strings.Join(tvs, ","))
}

func line(name, prefix, field string, vals ...int) string {
	var sb strings.Builder
	sb.Write(orgBucketID[:])
	tags(&sb, name, prefix, field, vals)
	sb.WriteString("#!~#")
	sb.WriteString(field)
	return sb.String()
}

func seriesGeneratorString(sg SeriesGenerator) []string {
	var lines []string
	for sg.Next() {
		lines = append(lines, fmt.Sprintf("%s#!~#%s", string(sg.Key()), string(sg.Field())))
	}
	return lines
}

func TestNewMergedSeriesGenerator(t *testing.T) {
	tests := []struct {
		n   string
		s   []SeriesGenerator
		exp []string
	}{
		{
			n: "single",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2, 1),
			},
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f0", 1, 0),
			},
		},
		{
			n: "multiple,interleaved",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2, 1),
				sg("cpu", "t", "f1", 2, 1),
			},
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f1", 0, 0),
				line("cpu", "t", "f0", 1, 0),
				line("cpu", "t", "f1", 1, 0),
			},
		},
		{
			n: "multiple,sequential",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2),
				sg("cpu", "u", "f0", 2, 1),
			},
			exp: []string{
				line("cpu", "t", "f0", 0),
				line("cpu", "t", "f0", 1),
				line("cpu", "u", "f0", 0, 0),
				line("cpu", "u", "f0", 1, 0),
			},
		},
		{
			n: "multiple,sequential",
			s: []SeriesGenerator{
				sg("m1", "t", "f0", 2, 1),
				sg("m0", "t", "f0", 2, 1),
			},
			exp: []string{
				line("m0", "t", "f0", 0, 0),
				line("m0", "t", "f0", 1, 0),
				line("m1", "t", "f0", 0, 0),
				line("m1", "t", "f0", 1, 0),
			},
		},
		{
			// ensure duplicates are removed
			n: "duplicates",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2, 1),
				sg("cpu", "t", "f0", 2, 1),
			},
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f0", 1, 0),
			},
		},
		{
			// ensure duplicates are removed, but non-dupes from same SeriesGenerator
			// are still included
			n: "duplicates,multiple,interleaved",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2, 1),
				sg("cpu", "t", "f1", 2, 1),
				sg("cpu", "t", "f0", 2, 1),
				sg("cpu", "t", "f1", 3, 1),
			},
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f1", 0, 0),
				line("cpu", "t", "f0", 1, 0),
				line("cpu", "t", "f1", 1, 0),
				line("cpu", "t", "f1", 2, 0),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.n, func(t *testing.T) {
			sg := NewMergedSeriesGenerator(tt.s)
			if got := seriesGeneratorString(sg); !cmp.Equal(got, tt.exp) {
				t.Errorf("unpexected -got/+exp\n%s", cmp.Diff(got, tt.exp))
			}
		})
	}
}

func TestNewMergedSeriesGeneratorLimit(t *testing.T) {
	tests := []struct {
		n   string
		s   []SeriesGenerator
		lim int64
		exp []string
	}{
		{
			n: "single",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 4, 1),
			},
			lim: 2,
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f0", 1, 0),
			},
		},
		{
			n: "multiple,interleaved",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2, 1),
				sg("cpu", "t", "f1", 2, 1),
			},
			lim: 3,
			exp: []string{
				line("cpu", "t", "f0", 0, 0),
				line("cpu", "t", "f1", 0, 0),
				line("cpu", "t", "f0", 1, 0),
			},
		},
		{
			n: "multiple,sequential",
			s: []SeriesGenerator{
				sg("cpu", "t", "f0", 2),
				sg("cpu", "u", "f0", 2, 1),
			},
			lim: 2,
			exp: []string{
				line("cpu", "t", "f0", 0),
				line("cpu", "t", "f0", 1),
			},
		},
		{
			n: "multiple,sequential",
			s: []SeriesGenerator{
				sg("m1", "t", "f0", 2, 1),
				sg("m0", "t", "f0", 2, 1),
			},
			lim: 4,
			exp: []string{
				line("m0", "t", "f0", 0, 0),
				line("m0", "t", "f0", 1, 0),
				line("m1", "t", "f0", 0, 0),
				line("m1", "t", "f0", 1, 0),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.n, func(t *testing.T) {
			sg := NewMergedSeriesGeneratorLimit(tt.s, tt.lim)
			if got := seriesGeneratorString(sg); !cmp.Equal(got, tt.exp) {
				t.Errorf("unpexected -got/+exp\n%s", cmp.Diff(got, tt.exp))
			}
		})
	}
}
