package errcodes

import (
	"net/http"
	"strings"
)

type ErrorCode struct {
	StatusCode int
	Message    string
	Details    string
}

func New(statusCode int, message string, details ...string) *ErrorCode {
	return &ErrorCode{
		StatusCode: statusCode,
		Message:    message,
		Details:    strings.Join(details, " "),
	}
}

func (e *ErrorCode) Error() string {
	return e.Message
}

// IsErr checks if the given error is an ErrorCode
func IsErr(err error) bool {
	_, ok := err.(*ErrorCode)
	return ok
}

// AsErr converts error to ErrorCode if possible
func AsErr(err error) (*ErrorCode, bool) {
	if err == nil {
		return nil, false
	}
	errcode, ok := err.(*ErrorCode)
	return errcode, ok
}

// Join combines multiple errors into a single ErrorCode
func Join(errs ...error) error {
	if len(errs) == 0 {
		return nil
	}

	var messages []string
	var details []string

	for _, err := range errs {
		if err == nil {
			continue
		}

		if errcode, ok := AsErr(err); ok {
			messages = append(messages, errcode.Message)
			if errcode.Details != "" {
				details = append(details, errcode.Details)
			}
		} else {
			messages = append(messages, err.Error())
		}
	}

	if len(messages) == 0 {
		return nil
	}

	// Use the first error's status code, or default to 500
	statusCode := http.StatusInternalServerError
	if len(errs) > 0 {
		if errcode, ok := AsErr(errs[0]); ok {
			statusCode = errcode.StatusCode
		}
	}

	return New(statusCode, strings.Join(messages, "; "), details...)
}

// Wrap wraps an error with additional context
func Wrap(err error, message string) error {
	if err == nil {
		return nil
	}

	if errcode, ok := AsErr(err); ok {
		return New(errcode.StatusCode, message, errcode.Message, errcode.Details)
	}

	return New(http.StatusInternalServerError, message, err.Error())
}

var (
	ErrNoDataset               = New(http.StatusNotFound, "no grib dataset found")
	ErrOutOfBounds             = New(http.StatusBadRequest, "requested time is out of bounds")
	ErrConfig                  = New(http.StatusInternalServerError, "configuration error")
	ErrConfigInvalidEnv        = New(http.StatusInternalServerError, "invalid environment configuration")
	ErrConfigMissingRequired   = New(http.StatusInternalServerError, "missing required configuration")
	ErrRedis                   = New(http.StatusInternalServerError, "redis error")
	ErrRedisLockAlreadyLocked  = New(http.StatusConflict, "could not perform redis lock", "already locked")
	ErrRedisCacheMiss          = New(http.StatusNotFound, "cache miss", "key not found")
	ErrRedisCacheCorrupted     = New(http.StatusInternalServerError, "cache data corrupted", "invalid format")
	ErrDownload                = New(http.StatusInternalServerError, "download error")
	ErrProcessing              = New(http.StatusInternalServerError, "data processing error")
	ErrNoCubeFilesFound        = New(http.StatusNotFound, "no cube files found")
	ErrNoValidCubeFilesFound   = New(http.StatusNotFound, "no valid cube files found")
	ErrLatestCubeFileIsTooOld  = New(http.StatusNotFound, "latest cube file is too old")
	ErrScheduler               = New(http.StatusInternalServerError, "scheduler error")
	ErrSchedulerInvalidJob     = New(http.StatusBadRequest, "invalid job configuration")
	ErrSchedulerTimeoutTooLong = New(http.StatusBadRequest, "job timeout too long", "timeout cannot exceed interval")
)
