// Package errors provides a way to return detailed information
// for an RPC request error. The error is normally JSON encoded.
package errors // import "go.unistack.org/micro/v3/errors"

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

var (
	// ErrBadRequest returns then requests contains invalid data
	ErrBadRequest = &Error{Code: 400}
	// ErrUnauthorized returns then user have unauthorized call
	ErrUnauthorized = &Error{Code: 401}
	// ErrForbidden returns then user have not access the resource
	ErrForbidden = &Error{Code: 403}
	// ErrNotFound returns then user specify invalid endpoint
	ErrNotFound = &Error{Code: 404}
	// ErrMethodNotAllowed returns then user try to get invalid method
	ErrMethodNotAllowed = &Error{Code: 405}
	// ErrTimeout returns then timeout exceeded
	ErrTimeout = &Error{Code: 408}
	// ErrConflict returns then request create duplicate resource
	ErrConflict = &Error{Code: 409}
	// ErrInternalServerError returns then server cant process request because of internal error
	ErrInternalServerError = &Error{Code: 500}
	// ErNotImplemented returns then server does not have desired endpoint method
	ErNotImplemented = &Error{Code: 501}
	// ErrBadGateway returns then server cant process request
	ErrBadGateway = &Error{Code: 502}
	// ErrServiceUnavailable returns then service unavailable
	ErrServiceUnavailable = &Error{Code: 503}
	// ErrGatewayTimeout returns then server have long time to process request
	ErrGatewayTimeout = &Error{Code: 504}
)

// Error type
type Error struct {
	// ID holds error id or service, usually someting like my_service or id
	ID string
	// Detail holds some useful details about error
	Detail string
	// Status usually holds text of http status
	Status string
	// Code holds error code
	Code int32
}

// Error satisfies error interface
func (e *Error) Error() string {
	b, _ := json.Marshal(e)
	return string(b)
}

/*
// Generator struct holds id of error
type Generator struct {
	id string
}

// Generator can emit new error with static id
func NewGenerator(id string) *Generator {
	return &Generator{id: id}
}

func (g *Generator) BadRequest(format string, args ...interface{}) error {
	return BadRequest(g.id, format, args...)
}
*/

// New generates a custom error
func New(id, detail string, code int32) error {
	return &Error{
		ID:     id,
		Code:   code,
		Detail: detail,
		Status: http.StatusText(int(code)),
	}
}

// Parse tries to parse a JSON string into an error. If that
// fails, it will set the given string as the error detail.
func Parse(err string) *Error {
	e := &Error{}
	nerr := json.Unmarshal([]byte(err), e)
	if nerr != nil {
		e.Detail = err
	}
	return e
}

// BadRequest generates a 400 error.
func BadRequest(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   400,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(400),
	}
}

// Unauthorized generates a 401 error.
func Unauthorized(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   401,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(401),
	}
}

// Forbidden generates a 403 error.
func Forbidden(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   403,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(403),
	}
}

// NotFound generates a 404 error.
func NotFound(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   404,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(404),
	}
}

// MethodNotAllowed generates a 405 error.
func MethodNotAllowed(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   405,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(405),
	}
}

// Timeout generates a 408 error.
func Timeout(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   408,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(408),
	}
}

// Conflict generates a 409 error.
func Conflict(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   409,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(409),
	}
}

// InternalServerError generates a 500 error.
func InternalServerError(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   500,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(500),
	}
}

// NotImplemented generates a 501 error
func NotImplemented(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   501,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(501),
	}
}

// BadGateway generates a 502 error
func BadGateway(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   502,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(502),
	}
}

// ServiceUnavailable generates a 503 error
func ServiceUnavailable(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   503,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(503),
	}
}

// GatewayTimeout generates a 504 error
func GatewayTimeout(id, format string, args ...interface{}) error {
	return &Error{
		ID:     id,
		Code:   504,
		Detail: fmt.Sprintf(format, args...),
		Status: http.StatusText(504),
	}
}

// Equal tries to compare errors
func Equal(err1 error, err2 error) bool {
	verr1, ok1 := err1.(*Error)
	verr2, ok2 := err2.(*Error)

	if ok1 != ok2 {
		return false
	}

	if !ok1 {
		return err1 == err2
	}

	if verr1.Code != verr2.Code {
		return false
	}

	return true
}

// CodeIn return true if err has specified code
func CodeIn(err interface{}, codes ...int32) bool {
	var code int32
	switch verr := err.(type) {
	case *Error:
		code = verr.Code
	case int32:
		code = verr
	default:
		return false
	}

	for _, check := range codes {
		if code == check {
			return true
		}
	}

	return false
}

// FromError try to convert go error to *Error
func FromError(err error) *Error {
	if verr, ok := err.(*Error); ok && verr != nil {
		return verr
	}

	return Parse(err.Error())
}

// MarshalJSON returns error data
func (e *Error) MarshalJSON() ([]byte, error) {
	return e.Marshal()
}

// UnmarshalJSON set error data
func (e *Error) UnmarshalJSON(data []byte) error {
	return e.Unmarshal(data)
}

// ProtoMessage noop func
func (e *Error) ProtoMessage() {}

// Reset resets error
func (e *Error) Reset() {
	*e = Error{}
}

// String returns error as string
func (e *Error) String() string {
	return fmt.Sprintf(`{"id":"%s","detail":"%s","status":"%s","code":%d}`, addslashes(e.ID), addslashes(e.Detail), addslashes(e.Status), e.Code)
}

// Marshal returns error data
func (e *Error) Marshal() ([]byte, error) {
	return []byte(e.String()), nil
}

// Unmarshal set error data
func (e *Error) Unmarshal(data []byte) error {
	str := string(data)
	if len(data) < 41 {
		return fmt.Errorf("invalid data")
	}
	parts := strings.FieldsFunc(str[1:len(str)-1], func(r rune) bool {
		return r == ','
	})
	for _, part := range parts {
		nparts := strings.FieldsFunc(part, func(r rune) bool {
			return r == ':'
		})
		for idx := 0; idx < len(nparts)/2; idx += 2 {
			val := strings.Trim(nparts[idx+1], `"`)
			if len(val) == 0 {
				continue
			}
			switch {
			case nparts[idx] == `"id"`:
				e.ID = val
			case nparts[idx] == `"detail"`:
				e.Detail = val
			case nparts[idx] == `"status"`:
				e.Status = val
			case nparts[idx] == `"code"`:
				c, err := strconv.ParseInt(val, 10, 32)
				if err != nil {
					return err
				}
				e.Code = int32(c)
			}
			idx++
		}
	}
	return nil
}

func addslashes(str string) string {
	var buf bytes.Buffer
	for _, char := range str {
		switch char {
		case '\'', '"', '\\':
			buf.WriteRune('\\')
		}
		buf.WriteRune(char)
	}
	return buf.String()
}