package bizerr

import (
	"fmt"
	"sort"
)

var (
	// useHttpOK4all translate all err code into http.OK
	useHttpOK4all = false
)

// 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 (
	errCode2HttpStatus = map[ErrCode]int{
		ErrCodeOK: 200,

		ErrCodeParams:          400,
		ErrCodeAuth:            401,
		ErrCodePermission:      403,
		ErrCodeRecordNotFound:  404,
		ErrCodeRecordExist:     409,
		ErrCodeTooManyRequests: 429,
		ErrCodeUnknown:         500,

		ErrCodeTimeout: 504,

		businessErrCodeStart: 400,
	}
	customErrCodeMap = map[ErrCode]string{}
)

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

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

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

func PrintAllCustomErrCode() {

	codes := make([]ErrCode, 0, len(customErrCodeMap))
	for code := range customErrCodeMap {
		codes = append(codes, code)
	}
	sort.Slice(codes, func(i, j int) bool {
		return codes[i] < codes[j]
	})

	fmt.Printf("\t Error Code \n")
	for _, code := range codes {
		fmt.Printf("%-40s %d \n", customErrCodeMap[code], code)
	}
	fmt.Printf("\n\n")
}

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)
}

// MustRegisterHttpStatus register http status for specific error code, will panic if error code is already registered
func MustRegisterHttpStatus(code ErrCode, status int) {
	if _, ok := errCode2HttpStatus[code]; ok {
		panic("duplicate register http status")
	}

	errCode2HttpStatus[code] = status
}

// OverwriteHttpStatus overwrite http status for specific error code
func OverwriteHttpStatus(code ErrCode, status int) {
	errCode2HttpStatus[code] = status
}

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

	customErrCodeMap[code] = memo
}

func convert2Exception(code ErrCode) ErrCode {
	return code%exceptionOffset + exceptionOffset
}

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
}
