package api

import (
	"encoding/base64"
	"encoding/json"

	"github.com/gin-gonic/gin"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/datasource"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap/errors"
	"github.com/samber/mo"

	"github.com/pingcap-inc/chaosAPI/pkg/wrapper/ginWrapper"
	"github.com/pingcap-inc/chaosAPI/pkg/wrapper/strconv"
)

type GRPCBackendArgs struct {
	ClusterID    uint64            `json:"cluster_id"`
	ProjectID    uint64            `json:"project_id"`
	ExperimentID mo.Option[uint64] `json:"experiment_id"`

	UserID uint64 `json:"user_id"`
}

func HandleUserToken(c *gin.Context) (*session.CtxUser, error) {
	data, err := base64.StdEncoding.DecodeString(c.GetHeader(session.CtxUserKey))
	if err != nil {
		return nil, errors.Errorf("decode ClinicUserKey failed, with error: %s", err.Error())
	}

	var u session.CtxUser
	if err := json.Unmarshal(data, &u); err != nil {
		return nil, errors.Errorf("decode ClinicUserKey to session.CtxUser failed, with error: %s", err.Error())
	}

	return &u, nil
}

func GetCluster(c *gin.Context, user *session.CtxUser) (*db.Cluster, error) {
	clusterIDRaw := c.Param("cid")
	if clusterIDRaw == "" {
		return nil, errors.New("cluster id not found")
	}

	orgIDUint64, err := ginWrapper.IntegerParam[uint64](c, "oid").Get()
	if err != nil {
		return nil, errors.Errorf("get org id failed, with error: %s", err.Error())
	}

	return datasource.GetCluster(user, clusterIDRaw, uint(orgIDUint64))
}

// GetArgs get GRPCBackendArgs from gin.Context
// First get session.CtxUser from header session.CtxUserKey
// Then get db.Cluster by org id, cluster id in path param and user
// Finally construct GRPCBackendArgs
func GetArgs(c *gin.Context) (*GRPCBackendArgs, error) {
	user, err := HandleUserToken(c)
	if err != nil {
		return nil, err
	}

	cluster, err := GetCluster(c, user)
	if err != nil {
		return nil, err
	}

	clusterIDUint64, err := strconv.ToInteger[uint64](cluster.ClusterID, 10)
	if err != nil {
		return nil, errors.Errorf("cluster id %s is not a valid uint64, with error: %s", cluster.ClusterID, err.Error())
	}

	projectIDUint64, err := strconv.ToInteger[uint64](cluster.ProjectID, 10)
	if err != nil {
		return nil, errors.Errorf("project id %s is not a valid uint64, with error: %s", cluster.ProjectID, err.Error())
	}

	if ginWrapper.HasParam(c, "eid") {
		experimentID, err := ginWrapper.IntegerParam[uint64](c, "eid").Get()
		if err != nil {
			return nil, errors.Errorf("get experiment id failed, with error: %s", err.Error())
		}
		return &GRPCBackendArgs{
			ClusterID:    clusterIDUint64,
			ProjectID:    projectIDUint64,
			ExperimentID: mo.Some[uint64](experimentID),
			UserID:       uint64(user.UserID),
		}, nil
	}

	return &GRPCBackendArgs{
		ClusterID:    clusterIDUint64,
		ProjectID:    projectIDUint64,
		ExperimentID: mo.None[uint64](),
		UserID:       uint64(user.UserID),
	}, nil
}
