package ginparser

import (
	dto "dataservices/internal/dto/dataapi"
	"fmt"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/errs"
	tierr "github.com/tidbcloud/tiinf/errs"
	"k8s.io/apimachinery/pkg/api/errors"
)

const (
	CONTEXT_CURRENT_USER       = "current_user"
	CONTEXT_ACCOUNT_USER       = "account_user"
	CONTEXT_AUTH0_TOKEN        = "auth0_token"
	CONTEXT_DATAAPP_JWT_CLAIMS = "dataapp_jwt_claims"
)

const (
	PARAM_ORG_ID               = "org_id"
	PARAM_PROJECT_ID           = "project_id"
	PARAM_APP_ID               = "app_id"
	PARAM_APIKEY_ID            = "apikey_id"
	PARAM_ENDPOINT_ID          = "endpoint_id"
	PARAM_CLUSTER_ID           = "cluster_id"
	PARAM_VERSION              = "version"
	PARAM_REPO_ID              = "repo_id"
	PARAM_PREFLIGHT_REQUEST_ID = "request_id"
	PARAM_DEPLOY_LOGID         = "log_id"
	PARAM_EXTERNAL_APP_ID      = "external_app_id"
	PARAM_CUSTOM_DOMAIN_ID     = "custom_domain_id"
	ERRORID                    = 0
)

type CommonParams struct {
	OrgID              uint64
	ProjectID          uint64
	AppID              uint64
	APIKeyID           uint64
	EndpointID         uint64
	ClusterID          uint64
	Version            string
	RepoID             int64
	PreflightRequestId uint64
	DeployLogID        uint64
	ExternalAppId      string
	CustomDomainId     uint64
	User               *account.User
	JsonParams         map[string]interface{}
	paramStatus        map[string]bool

	ctx *gin.Context
}

func parseUint64Key(c *gin.Context, key string) (uint64, bool) {
	idStr := c.Param(key)
	id, err := strconv.ParseUint(idStr, 10, 64)
	if err != nil {
		return ERRORID, false
	}
	return id, true
}

func parseInt64Key(c *gin.Context, key string) (int64, bool) {
	idStr := c.Param(key)
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return ERRORID, false
	}
	return id, true
}

func parseStringKey(c *gin.Context, key string) (string, bool) {
	value := c.Param(key)
	return value, value != ""
}

func NewCommonParamsByGin(c *gin.Context) *CommonParams {
	s := &CommonParams{OrgID: ERRORID, ProjectID: ERRORID, paramStatus: map[string]bool{}, JsonParams: map[string]interface{}{}}
	s.OrgID, s.paramStatus[PARAM_ORG_ID] = parseUint64Key(c, PARAM_ORG_ID)
	s.ProjectID, s.paramStatus[PARAM_PROJECT_ID] = parseUint64Key(c, PARAM_PROJECT_ID)
	s.AppID, s.paramStatus[PARAM_APP_ID] = parseUint64Key(c, PARAM_APP_ID)
	s.APIKeyID, s.paramStatus[PARAM_APIKEY_ID] = parseUint64Key(c, PARAM_APIKEY_ID)
	s.EndpointID, s.paramStatus[PARAM_ENDPOINT_ID] = parseUint64Key(c, PARAM_ENDPOINT_ID)
	s.ClusterID, s.paramStatus[PARAM_CLUSTER_ID] = parseUint64Key(c, PARAM_CLUSTER_ID)
	s.Version, s.paramStatus[PARAM_VERSION] = parseStringKey(c, PARAM_VERSION)
	s.RepoID, s.paramStatus[PARAM_REPO_ID] = parseInt64Key(c, PARAM_REPO_ID)
	s.PreflightRequestId, s.paramStatus[PARAM_PREFLIGHT_REQUEST_ID] = parseUint64Key(c, PARAM_PREFLIGHT_REQUEST_ID)
	s.DeployLogID, s.paramStatus[PARAM_DEPLOY_LOGID] = parseUint64Key(c, PARAM_DEPLOY_LOGID)
	s.ExternalAppId, s.paramStatus[PARAM_EXTERNAL_APP_ID] = parseStringKey(c, PARAM_EXTERNAL_APP_ID)
	s.CustomDomainId, s.paramStatus[PARAM_CUSTOM_DOMAIN_ID] = parseUint64Key(c, PARAM_CUSTOM_DOMAIN_ID)
	user, ok := c.Get(CONTEXT_ACCOUNT_USER)
	if ok {
		s.User = user.(*account.User)
	}
	c.ShouldBindBodyWith(&s.JsonParams, binding.JSON)
	s.ctx = c
	return s
}

func (s *CommonParams) ValidateKeys(validateKeys *[]string) error {
	for _, val := range *validateKeys {
		if !s.paramStatus[val] {
			return tierr.NewBadRequest("Invalid param: "+val, nil)
		}
	}
	return nil
}

func (s *CommonParams) ValidateUrl(validateKeys *[]string) error {
	_, authErr := s.GetUserEmail()
	if authErr {
		return tierr.NewBadRequest("User email is required.", nil)
	}
	for _, val := range *validateKeys {
		if !s.paramStatus[val] {
			return tierr.NewBadRequest("Invalid param: "+val, nil)
		}
	}
	return nil
}
func (s *CommonParams) ValidateInput(inputKeys *[]string) error {
	for _, val := range *inputKeys {
		if _, ok := s.JsonParams[val]; !ok {
			return errors.NewBadRequest("Check param " + val + " failed.")
		}
	}
	return nil
}

func (s *CommonParams) ValidateAll(validateKeys *[]string, customKeys *[]string) error {
	commErr := s.ValidateUrl(validateKeys)
	if commErr != nil {
		return commErr
	}
	inputErr := s.ValidateInput(customKeys)
	if inputErr != nil {
		return inputErr
	}
	return nil
}

func (s *CommonParams) GetInput(key string) (interface{}, bool) {
	val, ok := s.JsonParams[key]
	return val, ok
}
func (s *CommonParams) GetInputString(key string) (string, bool) {
	val, ok := s.JsonParams[key]
	return val.(string), ok
}
func (s *CommonParams) GetInputInt(key string) (int, bool) {
	val, ok := s.JsonParams[key]
	return val.(int), ok
}
func (s *CommonParams) GetUserEmail() (string, bool) {
	if s.User == nil {
		return "", true
	}
	return s.User.Email, false
}

func (s *CommonParams) GetUserId() (uint64, bool) {
	if s.User == nil {
		return 0, true
	}
	return s.User.Id, false
}

func (s *CommonParams) BindParam(input interface{}) error {
	err := s.ctx.ShouldBindBodyWith(input, binding.JSON)
	if err != nil {
		err = errs.NewBadRequest("invalid parameter", err)
	}
	return err
	// arr, err := json.Marshal(s.JsonParams)
	// if err != nil {
	// 	return err
	// }
	// err = json.Unmarshal(arr, input)
	// if err != nil {
	// 	return err
	// }
	// return nil
}

func FailRes(c *gin.Context, err error) {
	c.JSON(http.StatusOK, &dto.BasicRes{
		Code:    http.StatusBadRequest,
		Message: fmt.Sprintf("%s", err),
	})
}

func FailResWithCodeMsg(c *gin.Context, code int, msg string) {
	c.JSON(code, &dto.BasicRes{
		Code:    code,
		Message: msg,
	})
}
