package api

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error22 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/config"
	_const "github.com/pingcap-inc/clinic/pkg/const"
	"github.com/pingcap-inc/clinic/pkg/ratelimit"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

const (
	DefaultBlockSizeInBytes = 6 * 1024 * 1024
	MaxUploadConcurrent     = 100
	MaxBlockSizeInBytes     = 5 * 1000 * 1000 * 1000
	MaxFileSizeInBytes      = 5 * 1000 * 1000 * 1000 * 1000
)

// InitExternalAPI is used for client with basic auth.
func InitExternalAPI(engine *gin.Engine, auth *auth.Auth, quotalimiter *resource_limit.QuotaLimiter, rateLimiter *ratelimit.RateLimiterMiddleware, cfg *config.Config) {
	router := engine.Group("/api/v1")
	router.Use(auth.MWTryAuth)
	router.Use(rateLimiter.Middleware)

	router.POST("/upload", func(context *gin.Context) {
		user := session.GetUser(context)
		if user == nil {
			_ = context.Error(error22.ErrBadRequest.New("invalid user"))
			return
		}

		externalParams := []common.Pair{
			{
				Key:   "sequence",
				Value: context.Query("partseq"),
			},
			{
				Key:   "length",
				Value: context.Query("partlen"),
			},
		}

		err := common.Proxy(context, cfg.GetUploadEndpoint(), &common.ApiUserContext{
			UserID: user.UserID,
			OrgID:  user.OrgIDInApiToken,
		}, _const.UploadPath, externalParams...)
		if err != nil {
			log.Error("proxy request failed", zap.Uint("user", user.UserID), zap.Uint("org", user.OrgIDInApiToken), zap.Error(err))
			_ = context.Error(error22.ErrInternal.New("internal error"))
			return
		}
	})

	router.POST("/precreate", func(context *gin.Context) {
		user := session.GetUser(context)
		if user == nil {
			_ = context.Error(error22.ErrBadRequest.New("invalid user"))
			return
		}

		fileLen, err := strconv.ParseInt(context.Query("fileLen"), 10, 64)
		if err != nil {
			log.Error("not correct filelen", zap.Error(err))
		}
		if !user.Internal && !quotalimiter.AskForResource(resource_limit.UploadTimeQuota, 1, uint64(user.UserID)) {
			_ = context.Error(error22.ErrPreConditionFailed.New("%s", resource_limit.UploadTimeQuota.Reason()))
			return
		}
		if !user.Internal && !quotalimiter.AskForResource(resource_limit.UploadSizeQuota, uint64(fileLen), user.OrgIDInApiToken) {
			_ = context.Error(error22.ErrPreConditionFailed.New("%s", resource_limit.UploadSizeQuota.Reason()))
			return
		}

		externalParams := []common.Pair{
			{
				Key:   "length",
				Value: context.Query("fileLen"),
			},
			{
				Key:   "filename",
				Value: context.Query("orignalName"),
			},
		}

		err = common.Proxy(context, cfg.GetUploadEndpoint(), &common.ApiUserContext{
			UserID: user.UserID,
			OrgID:  user.OrgIDInApiToken,
		}, _const.UploadPrecreatePath, externalParams...)
		if err != nil {
			log.Error("proxy request failed", zap.Uint("user", user.UserID), zap.Uint("org", user.OrgIDInApiToken), zap.Error(err))
			_ = context.Error(error22.ErrInternal.New("internal error"))
			return
		}
	})

	router.POST("/flush", func(context *gin.Context) {
		user := session.GetUser(context)
		if user == nil {
			_ = context.Error(error22.ErrBadRequest.New("invalid user"))
			return
		}

		err := common.Proxy(context, cfg.GetUploadEndpoint(), &common.ApiUserContext{
			UserID: user.UserID,
			OrgID:  user.OrgIDInApiToken,
		}, _const.UploadFlushPath)
		if err != nil {
			log.Error("proxy request failed", zap.Uint("user", user.UserID), zap.Uint("org", user.OrgIDInApiToken), zap.Error(err))
			_ = context.Error(error22.ErrInternal.New("internal error"))
			return
		}
	})
}

func getBlockSizeInBytes(fileSize int64) int64 {
	if int(fileSize/DefaultBlockSizeInBytes) < MaxUploadConcurrent {
		return DefaultBlockSizeInBytes
	}

	size := fileSize / MaxUploadConcurrent
	if size < MaxBlockSizeInBytes {
		return size
	}

	return MaxBlockSizeInBytes
}

func parseIssueNumber(key string) (uint, error) {
	re := regexp.MustCompile(`ONCALL-(\d+)`)
	xs := re.FindStringSubmatch(key)
	if len(xs) == 0 {
		return 0, fmt.Errorf("invalid key: %s", key)
	}
	num, err := strconv.ParseUint(xs[1], 10, 64)
	if err != nil {
		return 0, err
	}
	return uint(num), nil
}
