// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
	"encoding/json"
	"net/url"
	"reflect"
	"sort"
	"testing"
)

func TestFieldStatsURLs(t *testing.T) {
	tests := []struct {
		Service        *FieldStatsService
		ExpectedPath   string
		ExpectedParams url.Values
	}{
		{
			Service:        &FieldStatsService{},
			ExpectedPath:   "/_field_stats",
			ExpectedParams: url.Values{},
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsClusterLevel,
			},
			ExpectedPath:   "/_field_stats",
			ExpectedParams: url.Values{"level": []string{FieldStatsClusterLevel}},
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsIndicesLevel,
			},
			ExpectedPath:   "/_field_stats",
			ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}},
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsClusterLevel,
				index: []string{"index1"},
			},
			ExpectedPath:   "/index1/_field_stats",
			ExpectedParams: url.Values{"level": []string{FieldStatsClusterLevel}},
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsIndicesLevel,
				index: []string{"index1", "index2"},
			},
			ExpectedPath:   "/index1%2Cindex2/_field_stats",
			ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}},
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsIndicesLevel,
				index: []string{"index_*"},
			},
			ExpectedPath:   "/index_%2A/_field_stats",
			ExpectedParams: url.Values{"level": []string{FieldStatsIndicesLevel}},
		},
	}

	for _, test := range tests {
		gotPath, gotParams, err := test.Service.buildURL()
		if err != nil {
			t.Fatalf("expected no error; got: %v", err)
		}
		if gotPath != test.ExpectedPath {
			t.Errorf("expected URL path = %q; got: %q", test.ExpectedPath, gotPath)
		}
		if gotParams.Encode() != test.ExpectedParams.Encode() {
			t.Errorf("expected URL params = %v; got: %v", test.ExpectedParams, gotParams)
		}
	}
}

func TestFieldStatsValidate(t *testing.T) {
	tests := []struct {
		Service *FieldStatsService
		Valid   bool
	}{
		{
			Service: &FieldStatsService{},
			Valid:   true,
		},
		{
			Service: &FieldStatsService{
				fields: []string{"field"},
			},
			Valid: true,
		},
		{
			Service: &FieldStatsService{
				bodyJson: &FieldStatsRequest{
					Fields: []string{"field"},
				},
			},
			Valid: true,
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsClusterLevel,
				bodyJson: &FieldStatsRequest{
					Fields: []string{"field"},
				},
			},
			Valid: true,
		},
		{
			Service: &FieldStatsService{
				level: FieldStatsIndicesLevel,
				bodyJson: &FieldStatsRequest{
					Fields: []string{"field"},
				},
			},
			Valid: true,
		},
		{
			Service: &FieldStatsService{
				level: "random",
			},
			Valid: false,
		},
	}

	for _, test := range tests {
		err := test.Service.Validate()
		isValid := err == nil
		if isValid != test.Valid {
			t.Errorf("expected validity to be %v, got %v", test.Valid, isValid)
		}
	}
}

func TestFieldStatsRequestSerialize(t *testing.T) {
	req := &FieldStatsRequest{
		Fields: []string{"creation_date", "answer_count"},
		IndexConstraints: map[string]*FieldStatsConstraints{
			"creation_date": &FieldStatsConstraints{
				Min: &FieldStatsComparison{Gte: "2014-01-01T00:00:00.000Z"},
				Max: &FieldStatsComparison{Lt: "2015-01-01T10:00:00.000Z"},
			},
		},
	}
	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("marshaling to JSON failed: %v", err)
	}
	got := string(data)
	expected := `{"fields":["creation_date","answer_count"],"index_constraints":{"creation_date":{"min_value":{"gte":"2014-01-01T00:00:00.000Z"},"max_value":{"lt":"2015-01-01T10:00:00.000Z"}}}}`
	if got != expected {
		t.Errorf("expected\n%s\n,got:\n%s", expected, got)
	}
}

func TestFieldStatsRequestDeserialize(t *testing.T) {
	body := `{
		"fields" : ["creation_date", "answer_count"],
   	"index_constraints" : {
      "creation_date" : {
         "min_value" : {
            "gte" : "2014-01-01T00:00:00.000Z"
         },
         "max_value" : {
            "lt" : "2015-01-01T10:00:00.000Z"
         }
      }
   	}
	}`

	var request FieldStatsRequest
	if err := json.Unmarshal([]byte(body), &request); err != nil {
		t.Errorf("unexpected error during unmarshalling: %v", err)
	}

	sort.Sort(lexicographically{request.Fields})

	expectedFields := []string{"answer_count", "creation_date"}
	if !reflect.DeepEqual(request.Fields, expectedFields) {
		t.Errorf("expected fields to be %v, got %v", expectedFields, request.Fields)
	}

	constraints, ok := request.IndexConstraints["creation_date"]
	if !ok {
		t.Errorf("expected field creation_date, didn't find it!")
	}
	if constraints.Min.Lt != nil {
		t.Errorf("expected min value less than constraint to be empty, got %v", constraints.Min.Lt)
	}
	if constraints.Min.Gte != "2014-01-01T00:00:00.000Z" {
		t.Errorf("expected min value >= %v, found %v", "2014-01-01T00:00:00.000Z", constraints.Min.Gte)
	}
	if constraints.Max.Lt != "2015-01-01T10:00:00.000Z" {
		t.Errorf("expected max value < %v, found %v", "2015-01-01T10:00:00.000Z", constraints.Max.Lt)
	}
}

func TestFieldStatsResponseUnmarshalling(t *testing.T) {
	clusterStats := `{
		 "_shards": {
				"total": 1,
				"successful": 1,
				"failed": 0
		 },
		 "indices": {
				"_all": {
					 "fields": {
							"creation_date": {
								 "type": "date",
								 "max_doc": 1326564,
								 "doc_count": 564633,
								 "density": 42,
								 "sum_doc_freq": 2258532,
								 "sum_total_term_freq": -1,
								 "searchable": true,
								 "aggregatable": true,
								 "min_value":1483016404000,
								 "min_value_as_string": "2016-12-29T13:00:04.000Z",
								 "max_value":1484152326000,
								 "max_value_as_string": "2017-01-11T16:32:06.000Z"
							},
							"answer_count": {
								 "max_doc": 1326564,
								 "doc_count": 139885,
								 "density": 10,
								 "sum_doc_freq": 559540,
								 "sum_total_term_freq": -1,
								 "searchable": true,
								 "aggregatable": true,
								 "min_value":1483016404000,
								 "min_value_as_string": "2016-12-29T13:00:04.000Z",
								 "max_value":1484152326000,
								 "max_value_as_string": "2017-01-11T16:32:06.000Z"
							}
					 }
				}
		 }
	}`

	var response FieldStatsResponse
	if err := json.Unmarshal([]byte(clusterStats), &response); err != nil {
		t.Errorf("unexpected error during unmarshalling: %v", err)
	}

	stats, ok := response.Indices["_all"]
	if !ok {
		t.Errorf("expected _all to be in the indices map, didn't find it")
	}

	fieldStats, ok := stats.Fields["creation_date"]
	if !ok {
		t.Errorf("expected creation_date to be in the fields map, didn't find it")
	}
	if want, have := true, fieldStats.Searchable; want != have {
		t.Errorf("expected creation_date searchable to be %v, got %v", want, have)
	}
	if want, have := true, fieldStats.Aggregatable; want != have {
		t.Errorf("expected creation_date aggregatable to be %v, got %v", want, have)
	}
	if want, have := "2016-12-29T13:00:04.000Z", fieldStats.MinValueAsString; want != have {
		t.Errorf("expected creation_date min value string to be %q, got %q", want, have)
	}
}

type lexicographically struct {
	strings []string
}

func (l lexicographically) Len() int {
	return len(l.strings)
}

func (l lexicographically) Less(i, j int) bool {
	return l.strings[i] < l.strings[j]
}

func (l lexicographically) Swap(i, j int) {
	l.strings[i], l.strings[j] = l.strings[j], l.strings[i]
}
