package errgroup

import (
	"errors"
	"fmt"
	"strings"
	"testing"
)

func TestErrorError(t *testing.T) {
	testErr := errors.New("error")
	err := &Error{Err: testErr}
	if expected, got := testErr.Error(), err.Error(); expected != got {
		t.Fatalf("expected %s but got %s", expected, got)
	}
}

func TestErrorUnwrap(t *testing.T) {
	wrapped := errors.New("unwrap")

	err := &Error{Err: fmt.Errorf("this wraps:%w", wrapped)}
	if expected, got := wrapped, errors.Unwrap(err); expected != got {
		t.Fatalf("expected %#+v but got %#+v", expected, got)
	}

}
func TestErrorIs(t *testing.T) {
	testErr := errors.New("is")
	err := &Error{Err: fmt.Errorf("this is: %w", testErr)}
	if expected, got := true, errors.Is(err, testErr); expected != got {
		t.Fatalf("expected %v but got %v", expected, got)
	}
}

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

func TestErrorAs(t *testing.T) {
	testErr := &errorString{"as"}
	err := &Error{Err: testErr}
	if expected, got := true, errors.As(err, &testErr); expected != got {
		t.Fatalf("[testErr as err] expected %v but got %v", expected, got)
	}
	if expected, got := false, errors.As(testErr, &err); expected != got /* errorString does not implemeny As, so the std/default functionality will be applied */ {
		t.Fatalf("[err as testErr] expected %v but got %v", expected, got)
	}
}

func TestGroupError(t *testing.T) {
	g := New(0)
	tests := []string{"error 1", "error 2", "error 3"}
	for _, tt := range tests {
		g.Add(errors.New(tt))
	}

	if expected, got := strings.Join(tests, "\n"), g.Error(); expected != got {
		t.Fatalf("expected '%s' but got '%s'", expected, got)
	}
}

func TestGroup(t *testing.T) {
	const (
		apiErrorsType = iota + 1
		childAPIErrorsType
		childAPIErrors2Type  = "string type 1"
		childAPIErrors2Type1 = "string type 2"

		apiErrorsText        = "apiErrors error 1"
		childAPIErrorsText   = "apiErrors:child error 1"
		childAPIErrors2Text  = "apiErrors:child2 error 1"
		childAPIErrors2Text1 = "apiErrors:child2_1 error 1"
	)

	g := New(nil)
	apiErrorsGroup := g.Group(apiErrorsType)
	apiErrorsGroup.Errf(apiErrorsText)

	childAPIErrorsGroup := apiErrorsGroup.Group(childAPIErrorsType)
	childAPIErrorsGroup.Addf(childAPIErrorsText)
	childAPIErrorsGroup2 := apiErrorsGroup.Group(childAPIErrors2Type)
	childAPIErrorsGroup2.Addf(childAPIErrors2Text)
	childAPIErrorsGroup2Group1 := childAPIErrorsGroup2.Group(childAPIErrors2Type1)
	childAPIErrorsGroup2Group1.Addf(childAPIErrors2Text1)

	if apiErrorsGroup.Type != apiErrorsType {
		t.Fatal("invalid type")
	}

	if childAPIErrorsGroup.Type != childAPIErrorsType {
		t.Fatal("invalid type")
	}

	if childAPIErrorsGroup2.Type != childAPIErrors2Type {
		t.Fatal("invalid type")
	}

	if childAPIErrorsGroup2Group1.Type != childAPIErrors2Type1 {
		t.Fatal("invalid type")
	}

	if expected, got := 2, len(apiErrorsGroup.children); expected != got {
		t.Fatalf("expected %d but got %d", expected, got)
	}

	if expected, got := 0, len(childAPIErrorsGroup.children); expected != got {
		t.Fatalf("expected %d but got %d", expected, got)
	}

	if expected, got := 1, len(childAPIErrorsGroup2.children); expected != got {
		t.Fatalf("expected %d but got %d", expected, got)
	}

	if expected, got := 0, len(childAPIErrorsGroup2Group1.children); expected != got {
		t.Fatalf("expected %d but got %d", expected, got)
	}

	if expected, got := 1, apiErrorsGroup.index; expected != got {
		t.Fatalf("expected index %d but got %d", expected, got)
	}

	if expected, got := 2, childAPIErrorsGroup.index; expected != got {
		t.Fatalf("expected index %d but got %d", expected, got)
	}

	if expected, got := 3, childAPIErrorsGroup2.index; expected != got {
		t.Fatalf("expected index %d but got %d", expected, got)
	}

	if expected, got := 4, childAPIErrorsGroup2Group1.index; expected != got {
		t.Fatalf("expected index %d but got %d", expected, got)
	}

	t.Run("Error", func(t *testing.T) {
		if expected, got :=
			strings.Join([]string{apiErrorsText, childAPIErrorsText, childAPIErrors2Text, childAPIErrors2Text1}, delim), g.Error(); expected != got {
			t.Fatalf("expected '%s' but got '%s'", expected, got)
		}
	})

	t.Run("Walk", func(t *testing.T) {
		expectedEntries := 4
		_ = Walk(g, func(typ interface{}, err error) {
			g.IncludeChildren = false
			childAPIErrorsGroup.IncludeChildren = false
			childAPIErrorsGroup2.IncludeChildren = false
			childAPIErrorsGroup2Group1.IncludeChildren = false

			expectedEntries--
			var expected string

			switch typ {
			case apiErrorsType:
				expected = apiErrorsText
			case childAPIErrorsType:
				expected = childAPIErrorsText
			case childAPIErrors2Type:
				expected = childAPIErrors2Text
			case childAPIErrors2Type1:
				expected = childAPIErrors2Text1
			}

			if got := err.Error(); expected != got {
				t.Fatalf("[%v] expected '%s' but got '%s'", typ, expected, got)
			}
		})

		if expectedEntries != 0 {
			t.Fatalf("not valid number of errors [...%d]", expectedEntries)
		}

		g.IncludeChildren = true
		childAPIErrorsGroup.IncludeChildren = true
		childAPIErrorsGroup2.IncludeChildren = true
		childAPIErrorsGroup2Group1.IncludeChildren = true
	})
}
