package influx

import (
	"encoding/json"
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/influxdata/chronograf"
)

func TestTemplateReplace(t *testing.T) {
	tests := []struct {
		name  string
		query string
		vars  []chronograf.TemplateVar
		want  string
	}{
		{
			name:  "select with parameters",
			query: ":method: field1, :field: FROM :measurement: WHERE temperature > :temperature:",
			vars: []chronograf.TemplateVar{
				chronograf.TemplateVar{
					Var: ":temperature:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "csv",
							Value: "10",
						},
					},
				},
				chronograf.TemplateVar{
					Var: ":field:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "fieldKey",
							Value: "field2",
						},
					},
				},
				chronograf.TemplateVar{
					Var: ":method:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "csv",
							Value: "SELECT",
						},
					},
				},
				chronograf.TemplateVar{
					Var: ":measurement:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "csv",
							Value: `"cpu"`,
						},
					},
				},
			},
			want: `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10`,
		},
		{
			name:  "select with parameters and aggregates",
			query: `SELECT mean(:field:) FROM "cpu" WHERE :tag: = :value: GROUP BY :tag:`,
			vars: []chronograf.TemplateVar{
				chronograf.TemplateVar{
					Var: ":value:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "tagValue",
							Value: "howdy.com",
						},
					},
				},
				chronograf.TemplateVar{
					Var: ":tag:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "tagKey",
							Value: "host",
						},
					},
				},
				chronograf.TemplateVar{
					Var: ":field:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "fieldKey",
							Value: "field",
						},
					},
				},
			},
			want: `SELECT mean("field") FROM "cpu" WHERE "host" = 'howdy.com' GROUP BY "host"`,
		},
		{
			name:  "Non-existant parameters",
			query: `SELECT :field: FROM "cpu"`,
			want:  `SELECT :field: FROM "cpu"`,
		},
		{
			name:  "var without a value",
			query: `SELECT :field: FROM "cpu"`,
			vars: []chronograf.TemplateVar{
				chronograf.TemplateVar{
					Var: ":field:",
				},
			},
			want: `SELECT :field: FROM "cpu"`,
		},
		{
			name:  "var with unknown type",
			query: `SELECT :field: FROM "cpu"`,
			vars: []chronograf.TemplateVar{
				chronograf.TemplateVar{
					Var: ":field:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "who knows?",
							Value: "field",
						},
					},
				},
			},
			want: `SELECT :field: FROM "cpu"`,
		},
		{
			name:  "auto interval",
			query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "333",
							Type:  "points",
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
		},
		{
			name:  "auto interval",
			query: `SELECT derivative(mean(usage_idle),:interval:) from "cpu" where time > now() - 4320h group by time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "333",
							Type:  "points",
						},
					},
				},
			},
			want: `SELECT derivative(mean(usage_idle),46702s) from "cpu" where time > now() - 4320h group by time(46702s)`,
		},
		{
			name:  "auto group by",
			query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "333",
							Type:  "points",
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
		},
		{
			name:  "auto group by without duration",
			query: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "333",
							Type:  "points",
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`,
		},
		{
			name:  "auto group by with :dashboardTime:",
			query: `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "333",
							Type:  "points",
						},
					},
				},
				{
					Var: ":dashboardTime:",
					Values: []chronograf.TemplateValue{
						{
							Type:  "constant",
							Value: "now() - 4320h",
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`,
		},
		{
			name:  "auto group by failing condition",
			query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "38",
							Type:  "points",
						},
					},
				},
				{
					Var: ":dashboardTime:",
					Values: []chronograf.TemplateValue{
						{
							Value:    "now() - 1h",
							Type:     "constant",
							Selected: true,
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) FROM "cpu" WHERE time > now() - 1h GROUP BY time(94s)`,
		},
		{
			name:  "no template variables specified",
			query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`,
			want:  `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`,
		},
		{
			name:  "auto group by failing condition",
			query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "38",
							Type:  "points",
						},
					},
				},
				{
					Var: ":dashboardTime:",
					Values: []chronograf.TemplateValue{
						{
							Value:    "now() - 1h",
							Type:     "constant",
							Selected: true,
						},
					},
				},
			},
			want: `SELECT mean(usage_idle) FROM "cpu" WHERE time > now() - 1h GROUP BY time(94s)`,
		},
		{
			name:  "query with no template variables contained should return query",
			query: `SHOW DATABASES`,
			vars: []chronograf.TemplateVar{
				{
					Var: ":interval:",
					Values: []chronograf.TemplateValue{
						{
							Value: "115",
							Type:  "points",
						},
					},
				},
				{
					Var: ":dashboardTime:",
					Values: []chronograf.TemplateValue{
						{
							Value:    "now() - 1h",
							Type:     "constant",
							Selected: true,
						},
					},
				},
			},
			want: `SHOW DATABASES`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			now, err := time.Parse(time.RFC3339, "1985-10-25T00:01:00Z")
			if err != nil {
				t.Fatal(err)
			}
			got, err := TemplateReplace(tt.query, tt.vars, now)
			if err != nil {
				t.Fatalf("TestParse unexpected TemplateReplace error: %v", err)
			}
			if got != tt.want {
				t.Errorf("TestParse %s =\n%s\nwant\n%s", tt.name, got, tt.want)
			}
		})
	}
}

func Test_TemplateVarsUnmarshalling(t *testing.T) {
	req := `[
	{
		"tempVar": ":interval:",
		"values": [
			{
				"value": "333",
				"type":  "points"
			},
			{
				"value": "10",
				"type":  "reportingInterval"
			}
		]
	},
	{
		"tempVar": ":cpu:",
		"values": [
			{
				"type": "tagValue",
				"value": "cpu-total",
				"selected": false
			}
		]
	}
	]`

	want := []chronograf.TemplateVar{
		{
			Var: ":interval:",
			Values: []chronograf.TemplateValue{
				{
					Value: "333",
					Type:  "points",
				},
				{
					Value: "10",
					Type:  "reportingInterval",
				},
			},
		},
		{
			Var: ":cpu:",
			Values: []chronograf.TemplateValue{
				{
					Value:    "cpu-total",
					Type:     "tagValue",
					Selected: false,
				},
			},
		},
	}

	var got []chronograf.TemplateVar
	err := json.Unmarshal([]byte(req), &got)
	if err != nil {
		t.Fatal("Err unmarshaling:", err)
	}

	if !reflect.DeepEqual(got, want) {
		t.Errorf("UnmarshalJSON() = \n%#v\n want \n%#v\n", got, want)
	}
}

func Test_RenderTemplate(t *testing.T) {
	gbvTests := []struct {
		name       string
		query      string
		want       string
		resolution uint // the screen resolution to render queries into
	}{
		{
			name:       "relative time only lower bound with one day of duration",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d GROUP BY time(259s)",
		},
		{
			name:       "relative time offset by week",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d - 7d AND time < now() - 7d GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d - 7d AND time < now() - 7d GROUP BY time(259s)",
		},
		{
			name:       "relative time with relative upper bound with one minute of duration",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 3m  AND time < now() - 2m GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 3m  AND time < now() - 2m GROUP BY time(180ms)",
		},
		{
			name:       "relative time with relative lower bound and now upper with one day of duration",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d  AND time < now() GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d  AND time < now() GROUP BY time(259s)",
		},
		{
			name:       "absolute time with one minute of duration",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > '1985-10-25T00:01:00Z' and time < '1985-10-25T00:02:00Z' GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > '1985-10-25T00:01:00Z' and time < '1985-10-25T00:02:00Z' GROUP BY time(180ms)",
		},
		{
			name:       "absolute time with nano seconds and zero duration",
			query:      "SELECT mean(usage_idle) FROM cpu WHERE time > '2017-07-24T15:33:42.994Z' and time < '2017-07-24T15:33:42.994Z' GROUP BY time(:interval:)",
			resolution: 333,
			want:       "SELECT mean(usage_idle) FROM cpu WHERE time > '2017-07-24T15:33:42.994Z' and time < '2017-07-24T15:33:42.994Z' GROUP BY time(1ms)",
		},
		{
			name:  "query should be returned if there are no template variables",
			query: "SHOW DATABASES",
			want:  "SHOW DATABASES",
		},
	}

	for _, tt := range gbvTests {
		t.Run(tt.name, func(t *testing.T) {
			now, err := time.Parse(time.RFC3339, "1985-10-25T00:01:00Z")
			if err != nil {
				t.Fatal(err)
			}
			tvar := chronograf.TemplateVar{
				Var: ":interval:",
				Values: []chronograf.TemplateValue{
					{
						Value: fmt.Sprintf("%d", tt.resolution),
						Type:  "points",
					},
				},
			}

			got, err := RenderTemplate(tt.query, tvar, now)
			if err != nil {
				t.Fatalf("unexpected error rendering template %v", err)
			}

			if got != tt.want {
				t.Fatalf("%q - durations not equal! Want: %s, Got: %s", tt.name, tt.want, got)
			}
		})
	}
}
