package validate

import (
	"encoding/json"
	"errors"
)

type UnauthorizedError struct{}

func (err *UnauthorizedError) Error() string {
	return "unauthorized"
}

func IsUnauthorizedError(err error) bool {
	var re *UnauthorizedError
	return errors.As(err, &re)
}

func NewUnauthorizedError() error {
	return &UnauthorizedError{}
}

type InvalidRouteKeyError struct {
	key string
}

func (err *InvalidRouteKeyError) Error() string {
	return "invalid route key: " + err.key
}

func NewInvalidRouteKeyError(key string) error {
	return &InvalidRouteKeyError{key}
}

func IsInvalidRouteKeyError(err error) bool {
	var re *InvalidRouteKeyError
	return errors.As(err, &re)
}

// ErrorResponse is the form used for API responses from failures in the API.
type ErrorResponse struct {
	Error  string `json:"error"`
	Fields string `json:"fields,omitempty"`
}

// RequestError is used to pass an error during the request through the
// application with web specific context.
type RequestError struct {
	Err    error
	Status int
	Fields error
}

// NewRequestError wraps a provided error with an HTTP status code. This
// function should be used when handlers encounter expected errors.
func NewRequestError(err error, status int) error {
	return &RequestError{err, status, nil}
}

func (err *RequestError) Error() string {
	return err.Err.Error()
}

// IsRequestError checks if an error of type RequestError exists.
func IsRequestError(err error) bool {
	var re *RequestError
	return errors.As(err, &re)
}

// FieldError is used to indicate an error with a specific request field.
type FieldError struct {
	Field string `json:"field"`
	Error string `json:"error"`
}

// FieldErrors represents a collection of field errors.
type FieldErrors []FieldError

func (fe FieldErrors) Append(field, reason string) FieldErrors {
	return append(fe, FieldError{
		Field: field,
		Error: reason,
	})
}

func (fe FieldErrors) Nil() bool {
	return len(fe) == 0
}

// Error implments the error interface.
func (fe FieldErrors) Error() string {
	d, err := json.Marshal(fe)
	if err != nil {
		return err.Error()
	}
	return string(d)
}

func NewFieldErrors(errs ...FieldError) FieldErrors {
	return errs
}

func IsFieldError(err error) bool {
	v := FieldErrors{}
	return errors.As(err, &v)
}

// Cause iterates through all the wrapped errors until the root
// error value is reached.
func Cause(err error) error {
	root := err
	for {
		if err = errors.Unwrap(root); err == nil {
			return root
		}
		root = err
	}
}