package resource_limit

import (
	"sync"

	"github.com/pingcap/errors"

	_const "github.com/pingcap-inc/clinic/pkg/const"
)

type ResourceLimiter interface {
	// TriggerLimit return true if requested defaultQuota is allowed
	TriggerLimit(uint64, ...any) bool
	GetQuota(orgID uint) uint64
}

type IQuotaLimiter interface {
	AskForResource(resource resourceQuota, request uint64, option ...any) bool
	GetResourceLimiter(resource resourceQuota) (ResourceLimiter, error)
}

type NopQuotaLimiter struct{}

func (nql *NopQuotaLimiter) AskForResource(_ resourceQuota, _ uint64, _ ...any) bool {
	return true
}

func (nql *NopQuotaLimiter) GetResourceLimiter(_ resourceQuota) (ResourceLimiter, error) {
	return &NopResourceLimier{}, nil
}

type NopResourceLimier struct{}

func (nrl *NopResourceLimier) TriggerLimit(_ uint64, _ ...any) bool {
	return true
}

func (nrl *NopResourceLimier) GetQuota(_ uint) uint64 {
	return 0
}

type QuotaLimiter struct {
	sync.Mutex
	resource map[resourceQuota]ResourceLimiter
}

type Option func(limiter *QuotaLimiter)

func NewQuotaLimiter(opts ...Option) *QuotaLimiter {
	ql := &QuotaLimiter{
		resource: make(map[resourceQuota]ResourceLimiter),
	}
	for _, opt := range opts {
		opt(ql)
	}
	ql.load()
	return ql
}

func (l *QuotaLimiter) addResourceLimit(resource resourceQuota, rl ResourceLimiter) {
	l.Lock()
	defer l.Unlock()
	l.resource[resource] = rl
}

func (l *QuotaLimiter) AskForResource(resource resourceQuota, request uint64, option ...any) bool {
	// if resourceLimiter not registered, allow the defaultQuota request
	resourceLimiter, found := l.resource[resource]
	if !found {
		return true
	}
	return resourceLimiter.TriggerLimit(request, option...)
}

func (l *QuotaLimiter) GetResourceLimiter(resource resourceQuota) (ResourceLimiter, error) {
	r, ok := l.resource[resource]
	if !ok {
		return nil, errors.New("resource limiter not registered")
	}
	return r, nil
}

func (l *QuotaLimiter) load() {
	l.addResourceLimit(OrgCntQuota, &TenantLimiter{resource: 3})
	l.addResourceLimit(ClusterQuota, &ClusterLimiter{defaultQuota: 10})
	l.addResourceLimit(DataSizeQuota, &DataSizeLimiter{defaultQuota: _const.MaxFileSizePreClusterInBytes}) // 53687091200 bytes = 50GB
	l.addResourceLimit(UploadTimeQuota, &UploadCntLimiter{resource: 50})
	l.addResourceLimit(UploadSizeQuota, &UploadSizeLimiter{defaultQuota: 3221225472}) // 3221225472 bytes = 3GB
	l.addResourceLimit(CredentialQuota, &ApiTokenLimiter{resource: 1})
	l.addResourceLimit(UploadCntPerCluster, &UploadCntPerClusterLimiter{defaultQuota: 50})
	l.addResourceLimit(HealthReportCntPerCluster, &HealthReportCntPerClusterLimiter{defaultQuota: 10})
	l.addResourceLimit(CmpReportCntPerCluster, &CmpReportCntPerClusterLimiter{defaultQuota: 10})
	l.addResourceLimit(ReportCntPerCluster, &ReportCntPerClusterLimiter{defaultQuota: 10})
}

type resourceQuota uint8

func (q resourceQuota) String() string {
	if q >= resourceQuota(len(exceedReasonStrings)) {
		return "unknown quota exceed reason"
	}
	return exceedReasonStrings[q]
}

const (
	OrgCntQuota resourceQuota = iota
	ClusterQuota
	DataSizeQuota
	UploadTimeQuota
	UploadSizeQuota
	CredentialQuota
	UploadCntPerCluster
	DownloadCntPerCluster
	HealthReportCntPerCluster
	CmpReportCntPerCluster
	ReportCntPerCluster
)

var exceedReasonStrings = [...]string{
	OrgCntQuota:               "Number of Organizations reaches limit, you can create up to 3",
	ClusterQuota:              "Number of clusters reaches limit, you can create up to 10 clusters",
	DataSizeQuota:             "Data size reaches limit",
	UploadTimeQuota:           "upload reach limit for one day",
	UploadSizeQuota:           "File size reaches limit (up to 3GB)",
	CredentialQuota:           "api token reach limit",
	UploadCntPerCluster:       "Daily upload task for this cluster reaches limit (50 times per day)",
	DownloadCntPerCluster:     "download reaches limit",
	HealthReportCntPerCluster: "Reach daily report limit（10 per day)",
	CmpReportCntPerCluster:    "Reach daily report limit（10 per day)",
	ReportCntPerCluster:       "Reach daily report limit（10 per day)",
}

func (q resourceQuota) Reason() string {
	if q >= resourceQuota(len(exceedReasonStrings)) {
		return "unknown quota exceed reason"
	}
	return exceedReasonStrings[q]
}
