// Package dataplane add by blacknc <blacknc@163.com> at 2024/08/30 11:38:21
package dataplane

import (
	"context"
	"strings"

	"github.com/go-logr/logr"
	"github.com/pingcap/errors"

	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	"github.com/tidbcloud/eks-provider/controllers/utils/regionalservercli"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

type KubernetesConf struct {
	*cloudv1.Kubernetes
	// kubeConfigContent refers to the base64-encoded contents of the kubeconfig file used by Azure.
	KubeConfigContent string
}

type DataplaneProvider interface {
	IsDataplaneExist() bool
	GetCluster() *regionalpb.Cluster
	GetKubernetes() *KubernetesConf
	GetClusterRole() *regionalpb.ClusterRole
}

func NewDataplaneProvider(ctx context.Context, logger logr.Logger, tenantID, projectID, clusterID string, cfg *config.RootConfig) (DataplaneProvider, error) {
	// get cluster & clusterRole
	var cluster *regionalpb.Cluster
	var err error
	if clusterID != "" {
		clusterCli := regionalservercli.NewClusterServiceHTTPClient(cfg.RegionalServerServiceEndpoint)
		cluster, err = clusterCli.GetCluster(ctx, &regionalpb.GetClusterRequest{
			ClusterId: clusterID,
		})
		if err != nil {
			if !strings.Contains(err.Error(), "not found;HTTPSTATUSCODE:404") {
				return nil, errors.Annotatef(err, "failed to get cluster from regional server, cluster:%s", clusterID)
			}
			logger.Info("cluster not found, maybe has been deleted", "cluster", clusterID)
		}
	}

	// get cluster role
	var clusterRole *regionalpb.ClusterRole
	if cluster != nil {
		clusterRoleCli := regionalservercli.NewClusterServiceHTTPClient(cfg.RegionalServerServiceEndpoint)
		clusterRole, err = clusterRoleCli.GetClusterRole(ctx, &regionalpb.GetClusterRoleRequest{
			ClusterId: clusterID,
		})
		if err != nil {
			if !strings.Contains(err.Error(), "not found;HTTPSTATUSCODE:404") {
				return nil, errors.Annotatef(err, "failed to get cluster role from regional server, cluster:%s", clusterID)
			}
			logger.Info("cluster role not found, maybe has been deleted", "cluster", clusterID)
		}
	}

	// get appbox
	appBoxReq := &regionalpb.ListAPPBoxesRequest{
		TenantId:  tenantID,
		ProjectId: projectID,
		Statuses:  []regionalpb.APPBox_Status{regionalpb.APPBox_ACTIVE},
	}
	if cluster != nil {
		appBoxReq.ClusterId = clusterID
	}
	appboxCli := regionalservercli.NewAPPBoxServiceHTTPClient(cfg.RegionalServerServiceEndpoint)
	appboxs, err := appboxCli.ListAPPBoxs(ctx, appBoxReq)
	if err != nil {
		return nil, errors.Annotatef(err, "failed to list appbox from regional server, cluster:%s", clusterID)
	} else if len(appboxs.GetAppboxes()) == 0 {
		if cluster != nil {
			logger.Info("not found any active appbox for cluster, but cluster is not nil", "cluster", clusterID)
			return nil, errors.New("not found any active appbox for cluster, but cluster is not nil")
		}
		logger.Info("not found any active appbox for cluster, maybe appbox has been deleted", "cluster", clusterID)
	}

	return &dataplaneProviderImpl{
		cluster:     cluster,
		clusterRole: clusterRole,
		appboxs:     appboxs.GetAppboxes(),
		config:      cfg,
	}, nil
}

type dataplaneProviderImpl struct {
	cluster     *regionalpb.Cluster
	clusterRole *regionalpb.ClusterRole
	appboxs     []*regionalpb.APPBox
	config      *config.RootConfig
}

func (p *dataplaneProviderImpl) IsDataplaneExist() bool {
	return len(p.appboxs) > 0
}

func (p *dataplaneProviderImpl) GetCluster() *regionalpb.Cluster {
	return p.cluster
}

func (p *dataplaneProviderImpl) GetClusterRole() *regionalpb.ClusterRole {
	return p.clusterRole
}

func (p *dataplaneProviderImpl) GetKubernetes() *KubernetesConf {

	// return empty kubernetes configuration if appboxs has been deleted
	if len(p.appboxs) == 0 {
		return &KubernetesConf{Kubernetes: &cloudv1.Kubernetes{}}
	}

	kube := p.appboxs[0].GetKubernetes()

	cloudv1K8S := cloudv1.Kubernetes{
		Name:     kube.GetName(),
		Endpoint: kube.GetEndpoint(),
		CAData:   []byte(kube.GetCa()),
		Account:  kube.GetAccountId(),
		Network:  kube.GetVpc(),
		Provider: p.config.Provider,
		Version:  kube.GetVersion(),
	}
	switch p.config.Provider {
	case consts.ProviderTypeEKS:
		cloudv1K8S.Cloud.AWS = p.convertlAWSCloud(kube.GetAws())
	case consts.ProviderTypeGKE:
		cloudv1K8S.Cloud.GCP = p.convertlGCPCloud(kube.GetGcp())
	case consts.ProviderTypeAKS:
		cloudv1K8S.Cloud.Azure = p.convertlAzureCloud(kube.GetAzure())
	}
	return &KubernetesConf{
		Kubernetes:        &cloudv1K8S,
		KubeConfigContent: kube.GetKubeconfig(),
	}
}

func (p *dataplaneProviderImpl) convertlAWSCloud(awsKube *regionalpb.APPBox_Kubernetes_Aws) *cloudv1.CloudAWS {
	var (
		nodeSubnets       []cloudv1.AWSSubnet
		publicNLBSubnets  []cloudv1.AWSSubnet
		privateNLBSubnets []cloudv1.AWSSubnet
	)
	for _, subnet := range awsKube.GetNodeSubnets() {
		nodeSubnets = append(nodeSubnets, cloudv1.AWSSubnet{
			SubnetID: subnet.Id,
			Zone:     subnet.AzName,
		})
	}
	for _, subnet := range awsKube.GetPublicLoadbalancerSubnets() {
		publicNLBSubnets = append(publicNLBSubnets, cloudv1.AWSSubnet{
			SubnetID: subnet.Id,
			Zone:     subnet.AzName,
		})
	}
	for _, subnet := range awsKube.GetPrivateLoadbalancerSubnets() {
		privateNLBSubnets = append(privateNLBSubnets, cloudv1.AWSSubnet{
			SubnetID: subnet.Id,
			Zone:     subnet.AzName,
		})
	}
	return &cloudv1.CloudAWS{
		EKSSecurityGroup: awsKube.GetSecurityGroup(),
		AdminNodeRole:    awsKube.GetAdminNodeRole(),
		SubnetGroups: []cloudv1.AWSSubnetGroup{
			{
				Name:    cloudv1.AWSSubnetGroupNodes,
				Subnets: nodeSubnets,
			},
			{
				Name:    cloudv1.AWSSubnetGroupPublicNLB,
				Subnets: publicNLBSubnets,
			},
			{
				Name:    cloudv1.AWSSubnetGroupPrivateNLB,
				Subnets: privateNLBSubnets,
			},
		},
	}
}

func (p *dataplaneProviderImpl) convertlGCPCloud(gcpKube *regionalpb.APPBox_Kubernetes_Gcp) *cloudv1.CloudGCP {
	return &cloudv1.CloudGCP{
		NodeZones:   gcpKube.GetNodeZones(),
		AdminNodeSa: gcpKube.GetAdminNodeSa(),
	}
}

func (p *dataplaneProviderImpl) convertlAzureCloud(azureKube *regionalpb.APPBox_Kubernetes_Azure) *cloudv1.CloudAzure {
	cloudv1Azure := &cloudv1.CloudAzure{
		NodeZones:     azureKube.GetNodeZones(),
		ResourceGroup: azureKube.GetResourceGroup(),
	}
	for _, subnet := range azureKube.GetNodeSubnets() {
		cloudv1Azure.NodeSubnets = append(cloudv1Azure.NodeSubnets, cloudv1.AzureNodeSubnet{
			Name: subnet.GetId(),
		})
	}
	for _, subnet := range azureKube.GetPodSubnets() {
		cloudv1Azure.PodSubnets = append(cloudv1Azure.PodSubnets, cloudv1.AzurePodSubnet{
			Name: subnet.GetId(),
		})
	}
	return cloudv1Azure
}
