package bizerr

import (
	"fmt"
)

// ErrCode [IsException(1 digital)] + errorClass(1 digital) + serviceID(2 digital) + code(5 digital)
// IsException: 1=exception, not exist=error
// errorClass: 4=request error, 5=internal error, 6=business error
// serviceID: customized， 99 is reserved for commonly use
// code: all by customized, 0 and 99999 are reserved
// such as, 149900005(1-4-99-00005) indicator: an exception, a request error, a commonly used error code, a record exist error
type ErrCode int64

const (
	exceptionOffset        = 100000000
	maxCustomErrCodeOffset = 9900000
	maxSvcId               = 98

	ErrCodeOK ErrCode = 0

	RequestErrCodeStart       ErrCode = 40000000
	RequestErrCodeCustomStart ErrCode = 40000000
	RequestErrCodeCustomEnd   ErrCode = 49900000
	requestErrCodeEnd         ErrCode = 49999999
	ErrCodeParams             ErrCode = 49900001
	ErrCodeAuth               ErrCode = 49900002
	ErrCodePermission         ErrCode = 49900003
	ErrCodeRecordNotFound     ErrCode = 49900004
	ErrCodeRecordExist        ErrCode = 49900005
	ErrCodeIPBlocked          ErrCode = 49900006 // HTTP 403
	ErrCodeTooManyRequests    ErrCode = 49900007 // HTTP 429
	ErrCodeUnderMaintenance   ErrCode = 49900008 // HTTP 403

	InternalErrCodeStart       ErrCode = 50000000
	InternalErrCodeCustomStart ErrCode = 50000000
	InternalErrCodeCustomEnd   ErrCode = 59900000
	InternalErrCodeEnd         ErrCode = 59999999
	ErrCodeDB                  ErrCode = 59900001
	ErrCodeRedis               ErrCode = 59900003
	ErrCodeMQ                  ErrCode = 59900004
	ErrCodePanic               ErrCode = 59900101
	ErrCodeJsonMarshal         ErrCode = 59900102
	ErrCodeTimeout             ErrCode = 59900103
	ErrCodeUnknown             ErrCode = 59900104
	ErrCodeHTTP                ErrCode = 59900105
	ErrCodeGRPC                ErrCode = 59900106
	ErrCodeSvcBilling          ErrCode = 59900201
	ErrCodeSvcInfraAPI         ErrCode = 59900202
	ErrCodeSvcInfraMetering    ErrCode = 59900203
	ErrCodeSvcO11y             ErrCode = 59900204

	BusinessErrCodeStart          ErrCode = 60000000
	BusinessErrCodeCustomStart    ErrCode = 60000000
	BusinessErrCodeCustomEnd      ErrCode = 69900000
	BusinessErrCodeEnd            ErrCode = 69999999
	ErrCodeGeneralBusiness        ErrCode = 69900001 // general business error
	ErrCodeOptimisticLockConflict ErrCode = 69900002 // optimistic lock conflict
)

var customErrCodeMap = map[ErrCode]string{}

func newInternalErrCode(svcId, offset int, memo string) ErrCode {
	checkSvcIdAndOffset(svcId, offset)
	code := buildCustomErrCode(InternalErrCodeCustomStart, svcId, offset)
	checkAndSetCustomErrCode(code, memo)
	return code
}

func newBusinessErrCode(svcId, offset int, memo string) ErrCode {
	checkSvcIdAndOffset(svcId, offset)
	code := buildCustomErrCode(BusinessErrCodeCustomStart, svcId, offset)
	checkAndSetCustomErrCode(code, memo)
	return code
}

func checkSvcIdAndOffset(svcId, offset int) {
	if svcId <= 0 || svcId >= maxSvcId {
		panic("invalid svcId")
	}
	if offset <= 0 || offset >= maxCustomErrCodeOffset {
		panic("invalid offset")
	}
}

func buildCustomErrCode(start ErrCode, svcId, offset int) ErrCode {
	return start + ErrCode(svcId*100000) + ErrCode(offset)
}

func checkAndSetCustomErrCode(code ErrCode, memo string) {
	if _, ok := customErrCodeMap[code]; ok {
		panic(fmt.Sprintf("already registered errcode: %d", code))
	}

	customErrCodeMap[code] = memo
}

func convert2Error(code ErrCode) ErrCode {
	return code % exceptionOffset
}

func IsException(code ErrCode) bool {
	return code >= exceptionOffset
}

func IsRequestErr(code ErrCode) bool {
	errCode := convert2Error(code)
	return errCode > RequestErrCodeStart && errCode < requestErrCodeEnd
}

func IsInternalErr(code ErrCode) bool {
	errCode := convert2Error(code)
	return errCode > InternalErrCodeStart && errCode < InternalErrCodeEnd
}

func IsBusinessErr(code ErrCode) bool {
	errCode := convert2Error(code)
	return errCode > BusinessErrCodeStart && errCode < BusinessErrCodeEnd
}
