// Package agherr contains AdGuard Home's error handling helpers.
package agherr

import (
	"fmt"
	"strings"

	"github.com/AdguardTeam/golibs/log"
)

// Error is the constant error type.
type Error string

// Error implements the error interface for Error.
func (err Error) Error() (msg string) {
	return string(err)
}

// manyError is an error containing several wrapped errors.  It is created to be
// a simpler version of the API provided by github.com/joomcode/errorx.
type manyError struct {
	message    string
	underlying []error
}

// Many wraps several errors and returns a single error.
//
// TODO(a.garipov): Add formatting to message.
func Many(message string, underlying ...error) (err error) {
	err = &manyError{
		message:    message,
		underlying: underlying,
	}

	return err
}

// Error implements the error interface for *manyError.
func (e *manyError) Error() (msg string) {
	switch len(e.underlying) {
	case 0:
		return e.message
	case 1:
		return fmt.Sprintf("%s: %s", e.message, e.underlying[0])
	default:
		b := &strings.Builder{}

		// Ignore errors, since strings.(*Buffer).Write never returns
		// errors.  We don't use aghstrings.WriteToBuilder here since
		// this package should be importable for any other.
		_, _ = fmt.Fprintf(b, "%s: %s (hidden: %s", e.message, e.underlying[0], e.underlying[1])
		for _, u := range e.underlying[2:] {
			// See comment above.
			_, _ = fmt.Fprintf(b, ", %s", u)
		}

		// See comment above.
		_, _ = b.WriteString(")")

		return b.String()
	}
}

// Unwrap implements the hidden errors.wrapper interface for *manyError.
func (e *manyError) Unwrap() (err error) {
	if len(e.underlying) == 0 {
		return nil
	}

	return e.underlying[0]
}

// wrapper is a copy of the hidden errors.wrapper interface for tests, linting,
// etc.
type wrapper interface {
	Unwrap() error
}

// Annotate annotates the error with the message, unless the error is nil.  This
// is a helper function to simplify code like this:
//
//   func (f *foo) doStuff(s string) (err error) {
//           defer func() {
//                   if err != nil {
//                           err = fmt.Errorf("bad foo string %q: %w", s, err)
//                   }
//           }()
//
//           // …
//   }
//
// Instead, write:
//
//   func (f *foo) doStuff(s string) (err error) {
//           defer agherr.Annotate("bad foo string %q: %w", &err, s)
//
//           // …
//   }
//
// msg must contain the final ": %w" verb.
//
// TODO(a.garipov): Clearify the function usage.
func Annotate(msg string, errPtr *error, args ...interface{}) {
	if errPtr == nil {
		return
	}

	err := *errPtr
	if err != nil {
		args = append(args, err)

		*errPtr = fmt.Errorf(msg, args...)
	}
}

// LogPanic is a convinient helper function to log a panic in a goroutine.  It
// should not be used where proper error handling is required.
func LogPanic(prefix string) {
	if v := recover(); v != nil {
		if prefix != "" {
			log.Error("%s: recovered from panic: %v", prefix, v)

			return
		}

		log.Error("recovered from panic: %v", v)
	}
}