package kv_test

import (
	"context"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/influxdata/chronograf"
	"github.com/stretchr/testify/require"
)

// IgnoreFields is used because ID is created by BoltDB and cannot be predicted reliably
// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map
var diffOptions = cmp.Options{
	cmpopts.IgnoreFields(chronograf.Dashboard{}, "ID"),
	cmpopts.IgnoreFields(chronograf.DashboardCell{}, "ID"),
	cmpopts.EquateEmpty(),
}

func TestDashboardsStore_Add(t *testing.T) {
	type args struct {
		ctx       context.Context
		dashboard *chronograf.Dashboard
		addFirst  bool
	}
	tests := []struct {
		name    string
		args    args
		want    *chronograf.Dashboard
		wantErr bool
	}{
		{
			name: "Add new Dashboard",
			args: args{
				ctx: context.Background(),
				dashboard: &chronograf.Dashboard{
					Cells: []chronograf.DashboardCell{
						{
							Axes: map[string]chronograf.Axis{
								"x": {
									Bounds: []string{"1", "2"},
									Label:  "label",
									Prefix: "pref",
									Suffix: "suff",
									Base:   "10",
									Scale:  "log",
								},
							},
						},
					},
					Name: "best name",
				},
			},
			want: &chronograf.Dashboard{
				Cells: []chronograf.DashboardCell{
					{
						Axes: map[string]chronograf.Axis{
							"x": {
								Bounds: []string{"1", "2"},
								Label:  "label",
								Prefix: "pref",
								Suffix: "suff",
								Base:   "10",
								Scale:  "log",
							},
						},
						Queries:      []chronograf.DashboardQuery{},
						Type:         "line",
						CellColors:   []chronograf.CellColor{},
						FieldOptions: []chronograf.RenamableField{},
					},
				},
				Templates: []chronograf.Template{},
				Name:      "best name",
			},
		},
	}
	for _, tt := range tests {
		client, err := NewTestClient()
		if err != nil {
			t.Fatal(err)
		}
		defer client.Close()

		s := client.DashboardsStore()
		if tt.args.addFirst {
			_, _ = s.Add(tt.args.ctx, *tt.args.dashboard)
		}
		got, err := s.Add(tt.args.ctx, *tt.args.dashboard)
		if (err != nil) != tt.wantErr {
			t.Errorf("%q. DashboardsStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr)
			continue
		}

		if tt.wantErr {
			continue
		}

		got, err = s.Get(tt.args.ctx, chronograf.DashboardID(got.ID))
		if err != nil {
			t.Fatalf("failed to get Dashboard: %v", err)
		}
		if diff := cmp.Diff(&got, tt.want, diffOptions...); diff != "" {
			t.Errorf("%q. DashboardsStore.Add():\n-got/+want\ndiff %s", tt.name, diff)
		}
	}
}

func TestDashboardStore_All(t *testing.T) {
	type fields struct {
		dashboard chronograf.Dashboard
	}
	tests := []struct {
		name   string
		fields fields
		want   []chronograf.Dashboard
	}{
		{
			name: "simple",
			fields: fields{
				dashboard: chronograf.Dashboard{
					Cells: []chronograf.DashboardCell{
						{
							Axes: map[string]chronograf.Axis{
								"x": {
									Bounds: []string{"1", "2"},
									Label:  "label",
									Prefix: "pref",
									Suffix: "suff",
									Base:   "10",
									Scale:  "log",
								},
							},
						},
					},
					Name: "best name",
				},
			},
			want: []chronograf.Dashboard{
				{
					Cells: []chronograf.DashboardCell{
						{
							Axes: map[string]chronograf.Axis{
								"x": {
									Bounds: []string{"1", "2"},
									Label:  "label",
									Prefix: "pref",
									Suffix: "suff",
									Base:   "10",
									Scale:  "log",
								},
							},
							Queries:      []chronograf.DashboardQuery{},
							Type:         "line",
							CellColors:   []chronograf.CellColor{},
							FieldOptions: []chronograf.RenamableField{},
						},
					},
					Templates: []chronograf.Template{},
					Name:      "best name",
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client, err := NewTestClient()
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()

			s := client.DashboardsStore()
			ctx := context.Background()

			s.Add(ctx, tt.fields.dashboard)

			got, err := s.All(ctx)
			require.NoError(t, err)
			require.Equal(t, 1, len(got))

			if diff := cmp.Diff(got, tt.want, diffOptions...); diff != "" {
				t.Errorf("LayoutStore.All():\n-got/+want\ndiff %s", diff)
				return
			}
		})
	}
}

func TestDashboardStore_Delete(t *testing.T) {
	type fields struct {
		dashboards []chronograf.Dashboard
	}
	type wants struct {
		err error
	}
	tests := []struct {
		name   string
		fields fields
		wants  wants
	}{
		{
			name: "simple",
			fields: fields{
				dashboards: []chronograf.Dashboard{
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"1", "2"},
										Label:  "label",
										Prefix: "pref",
										Suffix: "suff",
										Base:   "10",
										Scale:  "log",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name",
					},
				},
			},
			wants: wants{
				err: nil,
			},
		},
		{
			name: "dashboard not found",
			fields: fields{
				dashboards: []chronograf.Dashboard{
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"1", "2"},
										Label:  "label",
										Prefix: "pref",
										Suffix: "suff",
										Base:   "10",
										Scale:  "log",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name",
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client, err := NewTestClient()
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()

			s := client.DashboardsStore()
			ctx := context.Background()

			var d chronograf.Dashboard
			for _, dashboard := range tt.fields.dashboards {
				d, _ = s.Add(ctx, dashboard)
			}

			err = s.Delete(ctx, d)
			err = s.Delete(ctx, d)
			if (err != nil) != (tt.wants.err != nil) {
				t.Errorf("DashboardsStore.Delete() error = %v, want error %v", err, tt.wants.err)
				return
			}
		})
	}
}

func TestDashboardStore_Get(t *testing.T) {
	type fields struct {
		dashboards []chronograf.Dashboard
	}
	type wants struct {
		dashboard chronograf.Dashboard
		err       error
	}
	tests := []struct {
		name   string
		fields fields
		wants  wants
	}{
		{
			name: "simple",
			fields: fields{
				dashboards: []chronograf.Dashboard{
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"1", "2"},
										Label:  "label",
										Prefix: "pref",
										Suffix: "suff",
										Base:   "10",
										Scale:  "log",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name",
					},
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"3", "4"},
										Label:  "label1",
										Prefix: "pref1",
										Suffix: "suff1",
										Base:   "101",
										Scale:  "log1",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line1",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name1",
					},
				},
			},
			wants: wants{
				dashboard: chronograf.Dashboard{
					Cells: []chronograf.DashboardCell{
						{
							Axes: map[string]chronograf.Axis{
								"x": {
									Bounds: []string{"3", "4"},
									Label:  "label1",
									Prefix: "pref1",
									Suffix: "suff1",
									Base:   "101",
									Scale:  "log1",
								},
							},
							Queries:      []chronograf.DashboardQuery{},
							Type:         "line1",
							CellColors:   []chronograf.CellColor{},
							FieldOptions: []chronograf.RenamableField{},
						},
					},
					Templates: []chronograf.Template{},
					Name:      "best name1",
				},
				err: nil,
			},
		},
		{
			name: "dashboard not found",
			fields: fields{
				dashboards: []chronograf.Dashboard{},
			},
			wants: wants{
				err: chronograf.ErrDashboardNotFound,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client, err := NewTestClient()
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()

			s := client.DashboardsStore()
			ctx := context.Background()

			var d chronograf.Dashboard
			for _, dashboard := range tt.fields.dashboards {
				d, _ = s.Add(ctx, dashboard)
			}

			if tt.wants.err != nil {
				s.Delete(ctx, d)
			}

			got, err := s.Get(ctx, d.ID)
			if (err != nil) != (tt.wants.err != nil) {
				t.Errorf("DashboardsStore.Get() error = %v, want error %v", err, tt.wants.err)
				return
			}

			if diff := cmp.Diff(got, tt.wants.dashboard, diffOptions...); diff != "" {
				t.Errorf("LayoutStore.Get():\n-got/+want\ndiff %s", diff)
				return
			}
		})
	}
}

func TestDashboardStore_Update(t *testing.T) {
	type fields struct {
		dashboards []chronograf.Dashboard
	}
	type wants struct {
		dashboard chronograf.Dashboard
		err       error
	}
	tests := []struct {
		name   string
		fields fields
		wants  wants
	}{
		{
			name: "simple",
			fields: fields{
				dashboards: []chronograf.Dashboard{
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"1", "2"},
										Label:  "label",
										Prefix: "pref",
										Suffix: "suff",
										Base:   "10",
										Scale:  "log",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name",
					},
					{
						Cells: []chronograf.DashboardCell{
							{
								Axes: map[string]chronograf.Axis{
									"x": {
										Bounds: []string{"3", "4"},
										Label:  "label1",
										Prefix: "pref1",
										Suffix: "suff1",
										Base:   "101",
										Scale:  "log1",
									},
								},
								Queries:      []chronograf.DashboardQuery{},
								Type:         "line1",
								CellColors:   []chronograf.CellColor{},
								FieldOptions: []chronograf.RenamableField{},
							},
						},
						Templates: []chronograf.Template{},
						Name:      "best name1",
					},
				},
			},
			wants: wants{
				dashboard: chronograf.Dashboard{
					Cells: []chronograf.DashboardCell{
						{
							Axes: map[string]chronograf.Axis{
								"x": {
									Bounds: []string{"5", "6"},
									Label:  "label2",
									Prefix: "pref2",
									Suffix: "suff2",
									Base:   "102",
									Scale:  "log2",
								},
							},
							Queries:      []chronograf.DashboardQuery{},
							Type:         "line2",
							CellColors:   []chronograf.CellColor{},
							FieldOptions: []chronograf.RenamableField{},
						},
					},
					Templates: []chronograf.Template{},
					Name:      "best name2",
				},

				err: nil,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			client, err := NewTestClient()
			if err != nil {
				t.Fatal(err)
			}
			defer client.Close()

			s := client.DashboardsStore()
			ctx := context.Background()

			var d chronograf.Dashboard
			for _, dashboard := range tt.fields.dashboards {
				d, _ = s.Add(ctx, dashboard)
			}

			id := d.ID
			d = tt.wants.dashboard
			d.ID = id

			err = s.Update(ctx, d)
			if (err != nil) != (tt.wants.err != nil) {
				t.Errorf("DashboardsStore.Update() error = %v, want error %v", err, tt.wants.err)
				return
			}

			got, _ := s.Get(ctx, d.ID)
			if diff := cmp.Diff(got, tt.wants.dashboard, diffOptions...); diff != "" {
				t.Errorf("LayoutStore.Update():\n-got/+want\ndiff %s", diff)
				return
			}
		})
	}
}
