// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0

package errors_test

import (
	"fmt"
	"strconv"
	"testing"

	"github.com/mainflux/mainflux/pkg/errors"
	"github.com/stretchr/testify/assert"
)

const level = 10

var (
	err0 = errors.New("0")
	err1 = errors.New("1")
	err2 = errors.New("2")
)

func TestError(t *testing.T) {
	cases := []struct {
		desc string
		err  error
		msg  string
	}{
		{
			desc: "level 0 wrapped error",
			err:  err0,
			msg:  "0",
		},
		{
			desc: "level 1 wrapped error",
			err:  wrap(1),
			msg:  message(1),
		},
		{
			desc: "level 2 wrapped error",
			err:  wrap(2),
			msg:  message(2),
		},
		{
			desc: fmt.Sprintf("level %d wrapped error", level),
			err:  wrap(level),
			msg:  message(level),
		},
	}

	for _, tc := range cases {
		errMsg := tc.err.Error()
		assert.Equal(t, tc.msg, errMsg, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.msg, errMsg))
	}
}

func TestContains(t *testing.T) {
	cases := []struct {
		desc      string
		container error
		contained error
		contains  bool
	}{
		{
			desc:      "nil contains nil",
			container: nil,
			contained: nil,
			contains:  true,
		},
		{
			desc:      "nil contains non-nil",
			container: nil,
			contained: err0,
			contains:  false,
		},
		{
			desc:      "non-nil contains nil",
			container: err0,
			contained: nil,
			contains:  false,
		},
		{
			desc:      "non-nil contains non-nil",
			container: err0,
			contained: err1,
			contains:  false,
		},
		{
			desc:      "res of errors.Wrap(err1, err0) contains err0",
			container: errors.Wrap(err1, err0),
			contained: err0,
			contains:  true,
		},
		{
			desc:      "res of errors.Wrap(err1, err0) contains err1",
			container: errors.Wrap(err1, err0),
			contained: err1,
			contains:  true,
		},
		{
			desc:      "res of errors.Wrap(err2, errors.Wrap(err1, err0)) contains err1",
			container: errors.Wrap(err2, errors.Wrap(err1, err0)),
			contained: err1,
			contains:  true,
		},
		{
			desc:      fmt.Sprintf("level %d wrapped error contains", level),
			container: wrap(level),
			contained: errors.New(strconv.Itoa(level / 2)),
			contains:  true,
		},
		{
			desc:      "superset wrapper error contains subset wrapper error",
			container: wrap(level),
			contained: wrap(level / 2),
			contains:  false,
		},
	}
	for _, tc := range cases {
		contains := errors.Contains(tc.container, tc.contained)
		assert.Equal(t, tc.contains, contains, fmt.Sprintf("%s: expected %v to contain %v\n", tc.desc, tc.container, tc.contained))
	}
}

func TestWrap(t *testing.T) {
	cases := []struct {
		desc      string
		wrapper   error
		wrapped   error
		contained error
		contains  bool
	}{
		{
			desc:      "err 1 wraps err 2",
			wrapper:   err1,
			wrapped:   err0,
			contained: err0,
			contains:  true,
		},
		{
			desc:      "err2 wraps err1 wraps err0 and contains err0",
			wrapper:   err2,
			wrapped:   errors.Wrap(err1, err0),
			contained: err0,
			contains:  true,
		},
		{
			desc:      "err2 wraps err1 wraps err0 and contains err1",
			wrapper:   err2,
			wrapped:   errors.Wrap(err1, err0),
			contained: err1,
			contains:  true,
		},
		{
			desc:      "nil wraps nil",
			wrapper:   nil,
			wrapped:   nil,
			contained: nil,
			contains:  true,
		},
		{
			desc:      "err0 wraps nil",
			wrapper:   err0,
			wrapped:   nil,
			contained: nil,
			contains:  false,
		},
		{
			desc:      "nil wraps err0",
			wrapper:   nil,
			wrapped:   err0,
			contained: err0,
			contains:  false,
		},
	}

	for _, tc := range cases {
		err := errors.Wrap(tc.wrapper, tc.wrapped)
		contains := errors.Contains(err, tc.contained)
		assert.Equal(t, tc.contains, contains, fmt.Sprintf("%s: expected %v to contain %v\n", tc.desc, tc.wrapper, tc.wrapped))
	}
}

func wrap(level int) error {
	if level == 0 {
		return errors.New(strconv.Itoa(level))
	}
	return errors.Wrap(errors.New(strconv.Itoa(level)), wrap(level-1))
}

// message generates error message of wrap() generated wrapper error.
func message(level int) string {
	if level == 0 {
		return "0"
	}
	return strconv.Itoa(level) + " : " + message(level-1)
}
