package serverlessaction

import (
	"context"
	"fmt"
	"strconv"

	opent "github.com/tidbcloud/devops/entity/operation"
	"github.com/tidbcloud/devops/pkg/service/operation/actions"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

// allowedPoolNamespaces is the whitelist of pool namespaces that serverless ops
// Actions are permitted to target.
var allowedPoolNamespaces = map[string]struct{}{
	"tidb-pool":                  {},
	"vip-tidb-pool":              {},
	"super-vip-tidb-pool":        {},
	"canary-tidb-pool":           {},
	"canary-vip-tidb-pool":       {},
	"canary-super-vip-tidb-pool": {},
	"export-tidb-pool":           {},
	"export-vip-tidb-pool":       {},
	"export-super-vip-tidb-pool": {},
}

// Output keys written by serverless restart Actions onto instance.Outputs.
const (
	outputProvider      opent.OutputName = "serverless_provider"
	outputRegion        opent.OutputName = "serverless_region"
	outputKubernetes    opent.OutputName = "serverless_kubernetes"
	outputNamespace     opent.OutputName = "serverless_namespace"
	outputKeyspaceToken opent.OutputName = "serverless_keyspace_token"
	outputForce         opent.OutputName = "force"
	outputTargets       opent.OutputName = "targets"
)

// resolvedCluster carries the data needed to invoke a serverless ops RPC.
type resolvedCluster struct {
	ClusterID      string
	Provider       string
	Region         string
	Kubernetes     string
	KeyspaceToken  string
	ProjectID      uint64
	OrganizationID uint64
}

// resolveServerlessCluster resolves a cluster_id param to the provider, region,
// kubernetes name required by ops-service routing. It enforces that the cluster
// is a serverless cluster.
func resolveServerlessCluster(ctx context.Context, comps *actions.Components, instance *opent.Instance) (*resolvedCluster, error) {
	clusterIDStr, err := actions.GetClusterIDStr(instance)
	if err != nil {
		return nil, err
	}

	clusterIDUint, err := strconv.ParseUint(clusterIDStr, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("cluster_id %q is not a valid uint64: %w", clusterIDStr, err)
	}

	cluster, err := comps.CoreSvc.Mgmt.GetCluster(ctx, clusterIDUint)
	if err != nil {
		return nil, fmt.Errorf("get mgmt cluster %s: %w", clusterIDStr, err)
	}
	if cluster.DeployType != mgmtv1.DeployType_DEPLOY_TYPE_SERVERLESS {
		return nil, fmt.Errorf("cluster %s is not a serverless cluster (deploy_type=%s)", clusterIDStr, cluster.DeployType.String())
	}

	details, err := comps.ServerlessCli.GetCluster(ctx,
		clusterIDStr,
		strconv.FormatUint(cluster.ProjectId, 10),
		strconv.FormatUint(cluster.OrganizationId, 10),
	)
	if err != nil {
		return nil, fmt.Errorf("get serverless cluster %s: %w", clusterIDStr, err)
	}
	if details == nil {
		return nil, fmt.Errorf("serverless cluster %s details not found", clusterIDStr)
	}
	if details.GetKubernetes() == "" {
		return nil, fmt.Errorf("cluster %s has no kubernetes name", clusterIDStr)
	}
	if details.GetProvider() == "" {
		return nil, fmt.Errorf("cluster %s has no provider", clusterIDStr)
	}
	if cluster.GetRegion() == "" {
		return nil, fmt.Errorf("cluster %s has no region", clusterIDStr)
	}

	return &resolvedCluster{
		ClusterID:      clusterIDStr,
		Provider:       details.GetProvider(),
		Region:         cluster.GetRegion(),
		Kubernetes:     details.GetKubernetes(),
		KeyspaceToken:  details.GetKeyspaceToken(),
		ProjectID:      cluster.ProjectId,
		OrganizationID: cluster.OrganizationId,
	}, nil
}

// isAllowedPoolNamespace reports whether ns is in the pool whitelist.
func isAllowedPoolNamespace(ns string) bool {
	_, ok := allowedPoolNamespaces[ns]
	return ok
}
