package errs

import (
	"errors"
	"fmt"
	"net/http"

	"google.golang.org/grpc/codes"
	"gorm.io/gorm"
)

var (
	TiErr *tiErr = &tiErr{}

	_ Error = (*tiErr)(nil)

	// errors defined in this library
	ErrBadRequest            = RegisterErrorType(TiErr, http.StatusBadRequest, ErrCodeBadRequest)                       // 400
	ErrUnauthorized          = RegisterErrorType(TiErr, http.StatusUnauthorized, ErrCodeUnauthorized)                   // 401
	ErrPaymentRequired       = RegisterErrorType(TiErr, http.StatusPaymentRequired, ErrCodePaymentRequired)             // 402
	ErrForbidden             = RegisterErrorType(TiErr, http.StatusForbidden, ErrCodeForbidden)                         // 403
	ErrNotFound              = RegisterErrorType(TiErr, http.StatusNotFound, ErrCodeNotFound)                           // 404
	ErrMethodNotAllowed      = RegisterErrorType(TiErr, http.StatusMethodNotAllowed, ErrCodeMethodNotAllowed)           // 405
	ErrNotAcceptable         = RegisterErrorType(TiErr, http.StatusNotAcceptable, ErrCodeNotAcceptable)                 // 406
	ErrRequestTimeout        = RegisterErrorType(TiErr, http.StatusRequestTimeout, ErrCodeRequestTimeout)               // 408
	ErrConflict              = RegisterErrorType(TiErr, http.StatusConflict, ErrCodeConflict)                           // 409
	ErrLengthRequired        = RegisterErrorType(TiErr, http.StatusLengthRequired, ErrCodeLengthRequired)               // 411
	ErrPreconditionFailed    = RegisterErrorType(TiErr, http.StatusPreconditionFailed, ErrCodePreconditionFailed)       // 412
	ErrRequestEntityTooLarge = RegisterErrorType(TiErr, http.StatusRequestEntityTooLarge, ErrCodeRequestEntityTooLarge) // 413
	ErrRequestURITooLong     = RegisterErrorType(TiErr, http.StatusRequestURITooLong, ErrCodeRequestURITooLong)         // 414
	ErrUnsupportedMediaType  = RegisterErrorType(TiErr, http.StatusUnsupportedMediaType, ErrCodeUnsupportedMediaType)   // 415
	ErrExpectationFailed     = RegisterErrorType(TiErr, http.StatusExpectationFailed, ErrCodeExpectationFailed)         // 417
	ErrTooManyRequests       = RegisterErrorType(TiErr, http.StatusTooManyRequests, ErrCodeTooManyRequests)             // 429
	ErrInternalServerError   = RegisterErrorType(TiErr, http.StatusInternalServerError, ErrCodeInternalServerError)     // 500
	ErrNotImplemented        = RegisterErrorType(TiErr, http.StatusNotImplemented, ErrCodeNotImplemented)               // 501
	ErrBadGateway            = RegisterErrorType(TiErr, http.StatusBadGateway, ErrCodeBadGateway)                       // 502
	ErrServiceUnavailable    = RegisterErrorType(TiErr, http.StatusServiceUnavailable, ErrCodeServiceUnavailable)       // 503

	// errors compatible for bizerr
	ErrRecordExist                    = RegisterErrorType(ErrConflict, http.StatusConflict, ErrCodeRecordExist)
	ErrIPBlocked                      = RegisterErrorType(ErrForbidden, http.StatusForbidden, ErrCodeIPBlocked)
	ErrUnderMaintenance               = RegisterErrorType(ErrForbidden, http.StatusForbidden, ErrCodeUnderMaintenance)
	ErrInternalDB                     = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeDB)
	ErrInternalRedis                  = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeRedis)
	ErrInternalMQ                     = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeMQ)
	ErrInternalPanic                  = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodePanic)
	ErrInternalJsonMarshal            = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeJsonMarshal)
	ErrInternalTimeout                = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeTimeout)
	ErrInternalUnknown                = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeUnknown)
	ErrInternalHTTP                   = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeHTTP)
	ErrInternalGRPC                   = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeGRPC)
	ErrInternalSvcBilling             = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeSvcBilling)
	ErrInternalSvcInfraAPI            = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeSvcInfraAPI)
	ErrInternalSvcInfraMetering       = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeSvcInfraMetering)
	ErrInternalGeneralBusiness        = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeGeneralBusiness)
	ErrInternalOptimisticLockConflict = RegisterErrorType(ErrInternalServerError, http.StatusInternalServerError, ErrCodeOptimisticLockConflict)

	topLevelServerErrors = []Error{ErrInternalServerError, ErrNotImplemented, ErrBadGateway, ErrServiceUnavailable}

	// HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table.
	// https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
	HTTPStatusConvTab = map[int]codes.Code{
		// 400 Bad Request - INTERNAL.
		http.StatusBadRequest: codes.Internal,
		// 401 Unauthorized  - UNAUTHENTICATED.
		http.StatusUnauthorized: codes.Unauthenticated,
		// 403 Forbidden - PERMISSION_DENIED.
		http.StatusForbidden: codes.PermissionDenied,
		// 404 Not Found - UNIMPLEMENTED.
		http.StatusNotFound: codes.Unimplemented,
		// 429 Too Many Requests - UNAVAILABLE.
		http.StatusTooManyRequests: codes.Unavailable,
		// 502 Bad Gateway - UNAVAILABLE.
		http.StatusBadGateway: codes.Unavailable,
		// 503 Service Unavailable - UNAVAILABLE.
		http.StatusServiceUnavailable: codes.Unavailable,
		// 504 Gateway timeout - UNAVAILABLE.
		http.StatusGatewayTimeout: codes.Unavailable,
	}
)

type Error interface {
	error
	fmt.Formatter

	BizCode() int64
	HTTPCode() int
	GrpcCode() codes.Code
	ErrMsg() string
	StackTrace() StackTrace
	Cause() error
}

func newTierr(base Error, httpCode int, bizCode int64, msg string, stackTrace *stack, cause error) *tiErr {
	return &tiErr{base, httpCode, bizCode, msg, callers(), cause}
}

type tiErr struct {
	base Error

	httpCode   int
	bizCode    int64
	msg        string
	stackTrace *stack
	cause      error
}

func (e *tiErr) Format(f fmt.State, verb rune) {
	switch verb {
	case 's':
		fmt.Fprintf(f, e.msg)
	case 'v':
		if f.Flag('+') {
			fmt.Fprintf(f, "%s(code %d, msg %s, cause %+v)", e, e.BizCode(), e.ErrMsg(), e.Cause())
		} else {
			fmt.Fprintf(f, e.msg)
		}
	case 'q':
		fmt.Fprintf(f, "%q", e.msg)
	}
}

func (e *tiErr) Error() string {
	return e.msg
}

func (e *tiErr) BizCode() int64 {
	return e.bizCode
}

func (e *tiErr) ErrMsg() string {
	return e.msg
}

func (e *tiErr) StackTrace() StackTrace {
	if e.stackTrace == nil {
		return nil
	}

	return e.stackTrace.StackTrace()
}

func (e *tiErr) HTTPCode() int {
	return e.httpCode
}

func (e *tiErr) GrpcCode() codes.Code {
	if _, ok := HTTPStatusConvTab[e.httpCode]; ok {
		return HTTPStatusConvTab[e.httpCode]
	}
	return codes.Unknown
}

func (e *tiErr) Cause() error {
	return e.cause
}

func (e *tiErr) Is(err error) bool {
	v, ok := err.(*tiErr)
	if !ok {
		return false
	}

	return e == v
}

func (e *tiErr) Unwrap() error {
	if e == TiErr {
		return nil
	}

	return e.base
}

func (e *tiErr) clone() *tiErr {
	return newTierr(e.base, e.httpCode, e.bizCode, e.msg, e.stackTrace, e.cause)
}

func (e *tiErr) withstack() *tiErr {
	ee := e.clone()
	ee.stackTrace = callers()

	return ee
}

func RegisterErrorType(baseErr Error, httpCode int, bizCode int64) Error {
	return newTierr(baseErr, httpCode, bizCode, fmt.Sprintf("baseErr: %s, httpCode: %d, bizCode: %d", baseErr, httpCode, bizCode), callers(), nil)
}

func NewTiError(httpCode int, bizCode int64, msg string, cause error) Error {
	return newTierr(TiErr, httpCode, bizCode, msg, nil, cause)
}

func NewError(baseErr Error, msg string, cause error) Error {
	ee, ok := baseErr.(*tiErr)
	if !ok {
		return baseErr
	}

	ee = ee.clone()
	ee.base = baseErr
	ee.msg = msg
	ee.cause = cause
	return ee.withstack()
}

func IsError(err error, target error) bool {
	return errors.Is(err, target)
}

func AsTiError(err error) (Error, bool) {
	if !IsError(err, TiErr) {
		return nil, false
	}

	var e Error = &tiErr{}
	if !errors.As(err, &e) {
		return nil, false
	}

	return e, true
}

func WrapError(err error, msg string) error {
	return fmt.Errorf("%s: %w", msg, err)
}

func WrapGORMError(err error, msg string) error {
	if err == nil {
		return nil
	}

	if errors.Is(err, TiErr) {
		return err
	}

	if err == gorm.ErrRecordNotFound {
		return NewNotFound(msg, err)
	} else {
		return NewInternalDB(msg, err)
	}
}

func IsServerError(err error) bool {
	for _, errType := range topLevelServerErrors {
		if errors.Is(err, errType) {
			return true
		}
	}

	return false
}

func NewBadRequest(msg string, cause error) Error {
	return NewError(ErrBadRequest, msg, cause)
}

func NewUnauthorized(msg string, cause error) Error {
	return NewError(ErrUnauthorized, msg, cause)
}

func NewPaymentRequired(msg string, cause error) Error {
	return NewError(ErrPaymentRequired, msg, cause)
}

func NewForbidden(msg string, cause error) Error {
	return NewError(ErrForbidden, msg, cause)
}

func NewNotFound(msg string, cause error) Error {
	return NewError(ErrNotFound, msg, cause)
}

func NewMethodNotAllowed(msg string, cause error) Error {
	return NewError(ErrMethodNotAllowed, msg, cause)
}

func NewNotAcceptable(msg string, cause error) Error {
	return NewError(ErrNotAcceptable, msg, cause)
}

func NewRequestTimeout(msg string, cause error) Error {
	return NewError(ErrRequestTimeout, msg, cause)
}

func NewConflict(msg string, cause error) Error {
	return NewError(ErrConflict, msg, cause)
}

func NewLengthRequired(msg string, cause error) Error {
	return NewError(ErrLengthRequired, msg, cause)
}

func NewPreconditionFailed(msg string, cause error) Error {
	return NewError(ErrPreconditionFailed, msg, cause)
}

func NewRequestEntityTooLarge(msg string, cause error) Error {
	return NewError(ErrRequestEntityTooLarge, msg, cause)
}

func NewRequestURITooLong(msg string, cause error) Error {
	return NewError(ErrRequestURITooLong, msg, cause)
}

func NewUnsupportedMediaType(msg string, cause error) Error {
	return NewError(ErrUnsupportedMediaType, msg, cause)
}

func NewExpectationFailed(msg string, cause error) Error {
	return NewError(ErrExpectationFailed, msg, cause)
}

func NewTooManyRequests(msg string, cause error) Error {
	return NewError(ErrTooManyRequests, msg, cause)
}

func NewInternalServerError(msg string, cause error) Error {
	return NewError(ErrInternalServerError, msg, cause)
}

func NewNotImplemented(msg string, cause error) Error {
	return NewError(ErrNotImplemented, msg, cause)
}

func NewBadGateway(msg string, cause error) Error {
	return NewError(ErrBadGateway, msg, cause)
}

func NewServiceUnavailable(msg string, cause error) Error {
	return NewError(ErrServiceUnavailable, msg, cause)
}

func NewRecordExist(msg string, cause error) Error {
	return NewError(ErrRecordExist, msg, cause)
}

func NewIPBlocked(msg string, cause error) Error {
	return NewError(ErrIPBlocked, msg, cause)
}

func NewUnderMaintenance(msg string, cause error) Error {
	return NewError(ErrUnderMaintenance, msg, cause)
}

func NewInternalDB(msg string, cause error) Error {
	return NewError(ErrInternalDB, msg, cause)
}

func NewInternalRedis(msg string, cause error) Error {
	return NewError(ErrInternalRedis, msg, cause)
}

func NewInternalMQ(msg string, cause error) Error {
	return NewError(ErrInternalMQ, msg, cause)
}

func NewInternalPanic(msg string, cause error) Error {
	return NewError(ErrInternalPanic, msg, cause)
}

func NewInternalJsonMarshal(msg string, cause error) Error {
	return NewError(ErrInternalJsonMarshal, msg, cause)
}

func NewInternalTimeout(msg string, cause error) Error {
	return NewError(ErrInternalTimeout, msg, cause)
}

func NewInternalUnknown(msg string, cause error) Error {
	return NewError(ErrInternalUnknown, msg, cause)
}

func NewInternalHTTP(msg string, cause error) Error {
	return NewError(ErrInternalHTTP, msg, cause)
}

func NewInternalGRPC(msg string, cause error) Error {
	return NewError(ErrInternalGRPC, msg, cause)
}

func NewInternalSvcBilling(msg string, cause error) Error {
	return NewError(ErrInternalSvcBilling, msg, cause)
}

func NewInternalSvcInfraAPI(msg string, cause error) Error {
	return NewError(ErrInternalSvcInfraAPI, msg, cause)
}

func NewInternalSvcInfraMetering(msg string, cause error) Error {
	return NewError(ErrInternalSvcInfraMetering, msg, cause)
}

func NewInternalGeneralBusiness(msg string, cause error) Error {
	return NewError(ErrInternalGeneralBusiness, msg, cause)
}

func NewInternalOptimisticLockConflict(msg string, cause error) Error {
	return NewError(ErrInternalOptimisticLockConflict, msg, cause)
}
