package tidbcluster

import (
	"context"
	"fmt"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cert"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type clusterCR struct {
	cli     client.Client
	cluster *cloudv1.Cluster
	region  string
}

// NewClusterCRProvider creates a new clusterCR provider
// should implement the Provider interface with regional API calls
func NewClusterCRProvider(ctx context.Context, cli client.Client, namespace, clusterID, region string) (Provider, error) {
	cluster := &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: namespace,
			Name:      clusterID,
		},
	}
	if err := cli.Get(ctx, client.ObjectKeyFromObject(cluster), cluster); err != nil {
		return nil, err
	}
	return &clusterCR{cli: cli, cluster: cluster, region: region}, nil
}

func (p *clusterCR) GetNamespace(ctx context.Context, clusterID string) (string, error) {
	return fmt.Sprintf("tidb%s", clusterID), nil
}

func (p *clusterCR) FeatureEnabled(ctx context.Context, clusterID string, featureName string) (bool, error) {
	return p.cluster.FeatureEnabled(featureName), nil
}

func (p *clusterCR) GetCertCommonName(ctx context.Context, clusterID string) (string, error) {
	ns, err := p.GetNamespace(ctx, clusterID)
	if err != nil {
		return "", err
	}
	return util.FnvHash(ns), nil
}

func (p *clusterCR) GetCertIssuerName(ctx context.Context, clusterID string) (string, error) {
	enabled, err := p.FeatureEnabled(ctx, clusterID, consts.ClusterFeatureUseSelfSignedCertForTiDBComponents)
	if err != nil {
		return "", err
	}
	if enabled {
		return cert.TiDBComponentsSelfSignedIssuerName, nil
	} else {
		return cert.PCAIssuerName, nil
	}
}

func (p *clusterCR) GetClusterRole(ctx context.Context, clusterID string) (*ClusterRole, error) {
	switch p.cluster.GetProviderType() {
	case cloudv1.ProviderTypeEKS: // ref: `Context.GetAWSNodeRoleARN`
		roleArn, externalID := p.getTidbWorkerNodeRole()
		return &ClusterRole{CloudIamIdentity: roleArn, ExternalId: externalID}, nil
	case cloudv1.ProviderTypeGKE: // ref: `Context.GetGCPNodePoolServiceAccountEmail`
		return &ClusterRole{CloudIamIdentity: p.getTidbWorkerNodeServiceAccount()}, nil
	case cloudv1.ProviderTypeAKS:
		// AKS nodepool identity is k8s level, it's stored in KubernetesCluster CR
		// cluster-server is designed to not access KubernetesCluster CR directly
		// And this identity is not used by end users at the moment
		// So we just return an empty string here
		return &ClusterRole{CloudIamIdentity: ""}, nil
	}
	return nil, fmt.Errorf("unsupported provider type %s", p.cluster.GetProviderType())
}

// copy from cluster-server/server/cluster.go
func (p *clusterCR) getTidbWorkerNodeRole() (arn string, externalID string) {
	if p.cluster.Status.NodeIdentity != nil && p.cluster.Status.NodeIdentity.AWS != nil {
		arn := p.cluster.Status.NodeIdentity.AWS.RoleARN
		externalID := p.cluster.Status.NodeIdentity.AWS.ExternalID
		return arn, externalID
	}

	externalID = p.cluster.GetProjectID()
	roleName := namer.NewName(p.cluster.ClusterScopeName(), "ng-role", "", -1)

	// TODO: get external id
	if p.cluster.Spec.NetworkName != "" {
		networkName := p.cluster.Spec.NetworkName
		networkNamespace := p.cluster.Namespace
		externalID = namer.GenExternalID(networkNamespace, networkName)
		roleName = p.cluster.Spec.Kubernetes.Name
	}

	arn = util.GetRoleARN(p.cluster.Spec.Kubernetes.Account, roleName)
	return
}

// copy from cluster-server/server/cluster.go
func (p *clusterCR) getTidbWorkerNodeServiceAccount() string {
	if p.cluster.Status.NodeIdentity != nil && p.cluster.Status.NodeIdentity.GCP != nil {
		return p.cluster.Status.NodeIdentity.GCP.ServiceAccount
	}

	var saEmail string
	if p.cluster.Spec.NetworkName != "" {
		// .Spec.Kubernetes.Account is project on gcp
		gcpProject := p.cluster.Spec.Kubernetes.Account
		saName := namer.UniqueIdentifier(metav1.ObjectMeta{
			Namespace: p.cluster.Namespace,
			Name:      p.cluster.GetProviderType() + "-" + p.region,
		}, namer.GCPNodePoolServiceAccountNameOptions()...)
		saEmail = fmt.Sprintf("%s@%s.iam.gserviceaccount.com", saName, gcpProject)
	} else {
		saName := namer.NewName(p.cluster.Spec.Kubernetes.Name, "np-sa", "", namer.GKEServiceAccountNameLimit)
		saEmail = fmt.Sprintf("%s@%s.iam.gserviceaccount.com", saName, p.cluster.Spec.Kubernetes.Account)
	}
	return saEmail
}
