/*
 * Copyright (c) 2020 Baidu, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package errors

import (
	"fmt"
	"reflect"
	"sort"
	"testing"
)

func TestEmptyAggregate(t *testing.T) {
	var slice []error
	var agg Aggregate
	var err error

	agg = NewAggregate(slice)
	if agg != nil {
		t.Errorf("expected nil, got %#v", agg)
	}
	err = NewAggregate(slice)
	if err != nil {
		t.Errorf("expected nil, got %#v", err)
	}

	// This is not normally possible, but pedantry demands I test it.
	agg = aggregate(slice) // empty aggregate
	if s := agg.Error(); s != "" {
		t.Errorf("expected empty string, got %q", s)
	}
	if s := agg.Errors(); len(s) != 0 {
		t.Errorf("expected empty slice, got %#v", s)
	}
	err = agg.(error)
	if s := err.Error(); s != "" {
		t.Errorf("expected empty string, got %q", s)
	}
}

func TestAggregateWithNil(t *testing.T) {
	var slice []error
	slice = []error{nil}
	var agg Aggregate
	var err error

	agg = NewAggregate(slice)
	if agg != nil {
		t.Errorf("expected nil, got %#v", agg)
	}
	err = NewAggregate(slice)
	if err != nil {
		t.Errorf("expected nil, got %#v", err)
	}

	// Append a non-nil error
	slice = append(slice, fmt.Errorf("err"))
	agg = NewAggregate(slice)
	if agg == nil {
		t.Errorf("expected non-nil")
	}
	if s := agg.Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}
	if s := agg.Errors(); len(s) != 1 {
		t.Errorf("expected one-element slice, got %#v", s)
	}
	if s := agg.Errors()[0].Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}

	err = agg.(error)
	if err == nil {
		t.Errorf("expected non-nil")
	}
	if s := err.Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}
}

func TestSingularAggregate(t *testing.T) {
	var slice []error = []error{fmt.Errorf("err")}
	var agg Aggregate
	var err error

	agg = NewAggregate(slice)
	if agg == nil {
		t.Errorf("expected non-nil")
	}
	if s := agg.Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}
	if s := agg.Errors(); len(s) != 1 {
		t.Errorf("expected one-element slice, got %#v", s)
	}
	if s := agg.Errors()[0].Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}

	err = agg.(error)
	if err == nil {
		t.Errorf("expected non-nil")
	}
	if s := err.Error(); s != "err" {
		t.Errorf("expected 'err', got %q", s)
	}
}

func TestPluralAggregate(t *testing.T) {
	var slice []error = []error{fmt.Errorf("abc"), fmt.Errorf("123")}
	var agg Aggregate
	var err error

	agg = NewAggregate(slice)
	if agg == nil {
		t.Errorf("expected non-nil")
	}
	if s := agg.Error(); s != "[abc, 123]" {
		t.Errorf("expected '[abc, 123]', got %q", s)
	}
	if s := agg.Errors(); len(s) != 2 {
		t.Errorf("expected two-elements slice, got %#v", s)
	}
	if s := agg.Errors()[0].Error(); s != "abc" {
		t.Errorf("expected '[abc, 123]', got %q", s)
	}

	err = agg.(error)
	if err == nil {
		t.Errorf("expected non-nil")
	}
	if s := err.Error(); s != "[abc, 123]" {
		t.Errorf("expected '[abc, 123]', got %q", s)
	}
}

func TestFilterOut(t *testing.T) {
	testCases := []struct {
		err      error
		filter   []Matcher
		expected error
	}{
		{
			nil,
			[]Matcher{},
			nil,
		},
		{
			aggregate{},
			[]Matcher{},
			nil,
		},
		{
			aggregate{fmt.Errorf("abc")},
			[]Matcher{},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{fmt.Errorf("abc")},
			[]Matcher{func(err error) bool { return false }},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{fmt.Errorf("abc")},
			[]Matcher{func(err error) bool { return true }},
			nil,
		},
		{
			aggregate{fmt.Errorf("abc")},
			[]Matcher{func(err error) bool { return false }, func(err error) bool { return false }},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{fmt.Errorf("abc")},
			[]Matcher{func(err error) bool { return false }, func(err error) bool { return true }},
			nil,
		},
		{
			aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
			[]Matcher{func(err error) bool { return err.Error() == "def" }},
			aggregate{fmt.Errorf("abc"), fmt.Errorf("ghi")},
		},
		{
			aggregate{aggregate{fmt.Errorf("abc")}},
			[]Matcher{},
			aggregate{aggregate{fmt.Errorf("abc")}},
		},
		{
			aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
			[]Matcher{},
			aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
		},
		{
			aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
			[]Matcher{func(err error) bool { return err.Error() == "def" }},
			aggregate{aggregate{fmt.Errorf("abc")}},
		},
	}
	for i, testCase := range testCases {
		err := FilterOut(testCase.err, testCase.filter...)
		if !reflect.DeepEqual(testCase.expected, err) {
			t.Errorf("%d: expected %v, got %v", i, testCase.expected, err)
		}
	}
}

func TestFlatten(t *testing.T) {
	testCases := []struct {
		agg      Aggregate
		expected Aggregate
	}{
		{
			nil,
			nil,
		},
		{
			aggregate{},
			nil,
		},
		{
			aggregate{fmt.Errorf("abc")},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
			aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
		},
		{
			aggregate{aggregate{fmt.Errorf("abc")}},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{aggregate{aggregate{fmt.Errorf("abc")}}},
			aggregate{fmt.Errorf("abc")},
		},
		{
			aggregate{aggregate{fmt.Errorf("abc"), aggregate{fmt.Errorf("def")}}},
			aggregate{fmt.Errorf("abc"), fmt.Errorf("def")},
		},
		{
			aggregate{aggregate{aggregate{fmt.Errorf("abc")}, fmt.Errorf("def"), aggregate{fmt.Errorf("ghi")}}},
			aggregate{fmt.Errorf("abc"), fmt.Errorf("def"), fmt.Errorf("ghi")},
		},
	}
	for i, testCase := range testCases {
		agg := Flatten(testCase.agg)
		if !reflect.DeepEqual(testCase.expected, agg) {
			t.Errorf("%d: expected %v, got %v", i, testCase.expected, agg)
		}
	}
}

func TestCreateAggregateFromMessageCountMap(t *testing.T) {
	testCases := []struct {
		name     string
		mcm      MessageCountMap
		expected Aggregate
	}{
		{
			"input has single instance of one message",
			MessageCountMap{"abc": 1},
			aggregate{fmt.Errorf("abc")},
		},
		{
			"input has multiple messages",
			MessageCountMap{"abc": 2, "ghi": 1},
			aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
		},
		{
			"input has multiple messages",
			MessageCountMap{"ghi": 1, "abc": 2},
			aggregate{fmt.Errorf("abc (repeated 2 times)"), fmt.Errorf("ghi")},
		},
	}

	var expected, agg []error
	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			if testCase.expected != nil {
				expected = testCase.expected.Errors()
				sort.Slice(expected, func(i, j int) bool { return expected[i].Error() < expected[j].Error() })
			}
			if testCase.mcm != nil {
				agg = CreateAggregateFromMessageCountMap(testCase.mcm).Errors()
				sort.Slice(agg, func(i, j int) bool { return agg[i].Error() < agg[j].Error() })
			}
			if !reflect.DeepEqual(expected, agg) {
				t.Errorf("expected %v, got %v", expected, agg)
			}
		})
	}
}

func TestAggregateGoroutines(t *testing.T) {
	testCases := []struct {
		errs     []error
		expected map[string]bool // can't compare directly to Aggregate due to non-deterministic ordering
	}{
		{
			[]error{},
			nil,
		},
		{
			[]error{nil},
			nil,
		},
		{
			[]error{nil, nil},
			nil,
		},
		{
			[]error{fmt.Errorf("1")},
			map[string]bool{"1": true},
		},
		{
			[]error{fmt.Errorf("1"), nil},
			map[string]bool{"1": true},
		},
		{
			[]error{fmt.Errorf("1"), fmt.Errorf("267")},
			map[string]bool{"1": true, "267": true},
		},
		{
			[]error{fmt.Errorf("1"), nil, fmt.Errorf("1234")},
			map[string]bool{"1": true, "1234": true},
		},
		{
			[]error{nil, fmt.Errorf("1"), nil, fmt.Errorf("1234"), fmt.Errorf("22")},
			map[string]bool{"1": true, "1234": true, "22": true},
		},
	}
	for i, testCase := range testCases {
		funcs := make([]func() error, len(testCase.errs))
		for i := range testCase.errs {
			err := testCase.errs[i]
			funcs[i] = func() error { return err }
		}
		agg := AggregateGoroutines(funcs...)
		if agg == nil {
			if len(testCase.expected) > 0 {
				t.Errorf("%d: expected %v, got nil", i, testCase.expected)
			}
			continue
		}
		if len(agg.Errors()) != len(testCase.expected) {
			t.Errorf("%d: expected %d errors in aggregate, got %v", i, len(testCase.expected), agg)
			continue
		}
		for _, err := range agg.Errors() {
			if !testCase.expected[err.Error()] {
				t.Errorf("%d: expected %v, got aggregate containing %v", i, testCase.expected, err)
			}
		}
	}
}
