package errors

import (
	"errors"
	"fmt"

	"github.com/aws/smithy-go"
	pkg_errors "github.com/pkg/errors"
)

// extends default golang error interface
type ErrorWithCause interface {
	error
	Cause() error
	Code() int
}

type ErrorWithIs interface {
	error
	Is(specificError error) bool
}

func Error(message string) *CustomError {
	err := &CustomError{
		message: message,
		caller:  GetCaller(2),
		cause:   nil,
	}
	return err
}

func Errorf(format string, args ...interface{}) *CustomError {
	err := &CustomError{
		message: fmt.Sprintf(format, args...),
		caller:  GetCaller(2),
		cause:   nil,
	}
	return err
}

func Wrapf(err error, format string, args ...interface{}) *CustomError {
	if err == nil {
		return nil
	}

	wrappedErr := &CustomError{
		message: fmt.Sprintf(format, args...),
		caller:  GetCaller(2),
		cause:   err,
	}

	return wrappedErr
}

func Wrap(err error, msg string) *CustomError {
	if err == nil {
		return nil
	}

	wrappedErr := &CustomError{
		message: msg,
		caller:  GetCaller(2),
		cause:   err,
	}

	return wrappedErr
}

func ToCustomError(err error) *CustomError {
	if err == nil {
		return nil
	}

	wrappedErr := &CustomError{
		caller: GetCaller(2),
		cause:  err,
	}

	return wrappedErr
}

func HTTP(code int, err error, format string, args ...interface{}) *CustomError {
	wrappedErr := &CustomError{
		code:    code,
		message: fmt.Sprintf(format, args...),
		caller:  GetCaller(2),
		cause:   err,
	}

	return wrappedErr
}

func HTTPWithMsg(code int, format string, args ...interface{}) *CustomError {
	errorString := fmt.Sprintf(format, args...)

	wrappedErr := &CustomError{
		code:    code,
		message: errorString,
		caller:  GetCaller(2),
		cause:   Error(errorString),
	}

	return wrappedErr
}

func HTTPCodeOnly(code int) *CustomError {
	return HTTP(code, nil, "")
}

func HTTPWithError(code int, err error) *CustomError {
	var errorMessage string
	// This check is here just as a failsafe to seg faults, if err is nil then just return assign an empty string as message.
	if err != nil {
		errorMessage = err.Error()
	}

	wrappedErr := &CustomError{
		code:    code,
		message: errorMessage,
		caller:  GetCaller(2),
		cause:   err,
	}

	return wrappedErr
}

func AWSErrorExceptionCode(err error) string {
	if err == nil {
		return ""
	}

	var apiErr smithy.APIError
	if errors.As(err, &apiErr) {
		return apiErr.ErrorCode()
	}
	return ""
}

func AWSErrorWithoutExceptionCode(err error) error {
	if err == nil {
		return nil
	}

	var apiErr smithy.APIError
	if errors.As(err, &apiErr) {
		return Error(apiErr.ErrorMessage())
	}
	return err
}

type Description struct {
	Message string       `json:"error"`
	Source  *CallerInfo  `json:"source,omitempty"`
	Cause   *Description `json:"cause,omitempty"`
}

// from github.com/pkg/errors:
type stackTracer interface {
	StackTrace() pkg_errors.StackTrace
}

func As(err error, target any) bool {
	return errors.As(err, target)
}