package errs

import (
	"errors"
	"fmt"
	"strings"

	"backend/pkg/library/errs/code"

	"github.com/zeromicro/go-zero/core/logx"
	"google.golang.org/grpc/status"
)

const (
	defaultDetailCode = 00
)

func newBuiltinGRPCErr(scope, detail uint32, msg string) *LibError {
	return &LibError{
		category: code.CatGRPC,
		code:     detail,
		scope:    scope,
		msg:      msg,
	}
}

// FromError tries to let error as Err
// it supports to unwrap error that has Error
// return nil if failed to transfer
func FromError(err error) *LibError {
	if err == nil {
		return nil
	}

	var e *LibError
	if errors.As(err, &e) {
		return e
	}

	return nil
}

// FromCode parses code as following 7 碼
// Decimal: 1200314
// 12 represents Scope
// 003 represents Category
// 14 represents Detail error code
func FromCode(code uint32) *LibError {
	// 獲取 scope，前兩位數
	scope := code / 100000

	// 獲取 detail，最後兩位數
	detail := code % 100

	// 獲取 category，中間三位數
	category := (code / 100) % 1000

	return &LibError{
		category: category * 100,        // category 放大為三位數的整百數
		code:     category*100 + detail, // 重構完整的 code
		scope:    scope,
		msg:      "",
	}
}

// FromGRPCError transfer error to Err
// useful for gRPC client
func FromGRPCError(err error) *LibError {
	s, _ := status.FromError(err)
	e := FromCode(uint32(s.Code()))
	e.msg = s.Message()

	// For GRPC built-in code
	if e.Scope() == code.Unset && e.Category() == 0 && e.Code() != code.OK {
		e = newBuiltinGRPCErr(Scope, e.Code(), s.Message())
	}

	return e
}

/*** System ***/

// SystemTimeoutError xxx6300 returns Error 系統超時
func SystemTimeoutError(s ...string) *LibError {
	return NewError(Scope, code.SystemTimeoutError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// SystemTimeoutErrorL logs error message and returns Err
func SystemTimeoutErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := SystemTimeoutError(s...)
	if filed != nil {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// SystemInternalError xxx6100 returns Err struct
func SystemInternalError(s ...string) *LibError {
	return NewError(Scope, code.SystemInternalError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// SystemInternalErrorScope xxx6100 returns Err struct
func SystemInternalErrorScope(scope uint32, s ...string) *LibError {
	return NewError(scope, code.SystemInternalError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// SystemInternalErrorL logs error message and returns Err
func SystemInternalErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := SystemInternalError(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

/*** CatInput ***/

// InvalidFormat returns Err struct
func InvalidFormat(s ...string) *LibError {
	return NewError(Scope, code.InvalidFormat, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InvalidFormatL logs error message and returns Err
func InvalidFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidFormat(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InvalidRange returns Err struct
func InvalidRange(s ...string) *LibError {
	return NewError(Scope, code.InvalidRange, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InvalidRangeL logs error message and returns Err
func InvalidRangeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidRange(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// NotValidImplementation returns Err struct
func NotValidImplementation(s ...string) *LibError {
	return NewError(Scope, code.NotValidImplementation, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// NotValidImplementationL logs error message and returns Err
func NotValidImplementationL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := NotValidImplementation(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

/*** CatDB ***/

// DBError returns Err
func DBError(s ...string) *LibError {
	return NewError(Scope, code.DBError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

func DBErrorWithScope(scope uint32, s ...string) *LibError {
	return NewError(scope, code.DBError, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// DBErrorL logs error message and returns Err
func DBErrorL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := DBError(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())
	return e
}

// DBDataConvert returns Err
func DBDataConvert(s ...string) *LibError {
	return NewError(Scope, code.DBDataConvert, defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// DBDataConvertL logs error message and returns Err
func DBDataConvertL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := DBDataConvert(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// DBDuplicate returns Err
func DBDuplicate(s ...string) *LibError {
	return NewError(Scope, code.DBDuplicate, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// DBDuplicateL logs error message and returns Err
func DBDuplicateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := DBDuplicate(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

/*** CatResource ***/

// ResourceNotFound returns Err and logging
func ResourceNotFound(s ...string) *LibError {
	return NewError(Scope, code.ResourceNotFound, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceNotFoundL logs error message and returns Err
func ResourceNotFoundL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := ResourceNotFound(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InvalidResourceFormat returns Err
func InvalidResourceFormat(s ...string) *LibError {
	return NewError(Scope, code.InvalidResourceFormat, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InvalidResourceFormatL logs error message and returns Err
func InvalidResourceFormatL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidResourceFormat(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InvalidResourceState returns status not correct.
// for example: company should be destroy, agent should be no-sensor/fail-install ...
func InvalidResourceState(s ...string) *LibError {
	return NewError(Scope, code.InvalidResourceState, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InvalidResourceStateL logs error message and returns status not correct.
func InvalidResourceStateL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidResourceState(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

func ResourceInsufficient(s ...string) *LibError {
	return NewError(Scope, code.ResourceInsufficient, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

func ResourceInsufficientL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := ResourceInsufficient(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InsufficientPermission returns Err
func InsufficientPermission(s ...string) *LibError {
	return NewError(Scope, code.InsufficientPermission,
		defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InsufficientPermissionL returns Err and log
func InsufficientPermissionL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InsufficientPermission(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// UserSuspended returns Err
func UserSuspended(scope uint32, s ...string) *LibError {
	return NewError(scope, code.UserSuspended,
		defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceAlreadyExist returns Err
func ResourceAlreadyExist(s ...string) *LibError {
	return NewError(Scope, code.ResourceAlreadyExist, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceAlreadyExistWithScope returns Err
func ResourceAlreadyExistWithScope(scope uint32, s ...string) *LibError {
	return NewError(scope, code.ResourceAlreadyExist, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceAlreadyExistL logs error message and returns Err
func ResourceAlreadyExistL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := ResourceAlreadyExist(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InvalidMeasurementID returns Err
func InvalidMeasurementID(s ...string) *LibError {
	return NewError(Scope, code.InvalidMeasurementID, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InvalidMeasurementIDL logs error message and returns Err
func InvalidMeasurementIDL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidMeasurementID(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// ResourceExpired returns Err
func ResourceExpired(s ...string) *LibError {
	return NewError(Scope, code.ResourceExpired,
		defaultDetailCode, fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceExpiredL logs error message and returns Err
func ResourceExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := ResourceExpired(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// ResourceMigrated returns Err
func ResourceMigrated(s ...string) *LibError {
	return NewError(Scope, code.ResourceMigrated, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ResourceMigratedL logs error message and returns Err
func ResourceMigratedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := ResourceMigrated(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InsufficientQuota returns Err
func InsufficientQuota(s ...string) *LibError {
	return NewError(Scope, code.InsufficientQuota, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// InsufficientQuotaL logs error message and returns Err
func InsufficientQuotaL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InsufficientQuota(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

/*** CatAuth ***/

// Unauthorized returns Err
func Unauthorized(s ...string) *LibError {
	return NewError(Scope, code.Unauthorized, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// UnauthorizedL logs error message and returns Err
func UnauthorizedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := Unauthorized(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// AuthExpired returns Err
func AuthExpired(s ...string) *LibError {
	return NewError(Scope, code.AuthExpired, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// AuthExpiredL logs error message and returns Err
func AuthExpiredL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := AuthExpired(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// InvalidPosixTime returns Err
func InvalidPosixTime(s ...string) *LibError {
	return NewError(Scope, code.InvalidPosixTime, defaultDetailCode,
		fmt.Sprintf("i%s", strings.Join(s, " ")))
}

// InvalidPosixTimeL logs error message and returns Err
func InvalidPosixTimeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := InvalidPosixTime(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// SigAndPayloadNotMatched returns Err
func SigAndPayloadNotMatched(s ...string) *LibError {
	return NewError(Scope, code.SigAndPayloadNotMatched, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// SigAndPayloadNotMatchedL logs error message and returns Err
func SigAndPayloadNotMatchedL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := SigAndPayloadNotMatched(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// Forbidden returns Err
func Forbidden(s ...string) *LibError {
	return NewError(Scope, code.Forbidden, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// ForbiddenL logs error message and returns Err
func ForbiddenL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := Forbidden(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// IsAuthUnauthorizedError check the err is unauthorized error
func IsAuthUnauthorizedError(err *LibError) bool {
	switch err.Code() / 100 {
	case code.Unauthorized, code.AuthExpired, code.InvalidPosixTime,
		code.SigAndPayloadNotMatched, code.Forbidden,
		code.InvalidFormat, code.ResourceNotFound:
		return true
	default:
		return false
	}
}

/*** CatPubSub ***/

// Publish returns Err
func Publish(s ...string) *LibError {
	return NewError(Scope, code.Publish, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// PublishL logs error message and returns Err
func PublishL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := Publish(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())
	return e
}

// Consume returns Err
func Consume(s ...string) *LibError {
	return NewError(Scope, code.Consume, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

func ConsumeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := Consume(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}

// MsgSizeTooLarge returns Err
func MsgSizeTooLarge(s ...string) *LibError {
	return NewError(Scope, code.MsgSizeTooLarge, defaultDetailCode,
		fmt.Sprintf("%s", strings.Join(s, " ")))
}

// MsgSizeTooLargeL logs error message and returns Err
func MsgSizeTooLargeL(l logx.Logger, filed []logx.LogField, s ...string) *LibError {
	e := MsgSizeTooLarge(s...)
	if filed != nil || len(filed) >= 0 {
		l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
	}
	l.WithCallerSkip(1).Error(e.Error())

	return e
}
