package compat

import (
	"context"
	"strconv"

	"github.com/samber/lo"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

func (s *dataPlatformCompatServiceImpl) GetCompatCluster(
	ctx context.Context,
	request *globaldedicatedv1.GetCompatClusterRequest,
) (*globaldedicatedv1.CompatCluster, bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "Domain.GetCompatCluster")
	defer span.End()

	// get deleted cluster
	if request.ShowDeleted {
		cluster, err := s.clusterRepo.Get(ctx, request.ClusterId, true)
		if err != nil {
			return nil, err
		}

		if cluster.GetState() == globaldedicatedv1.Cluster_DELETED {
			return &globaldedicatedv1.CompatCluster{
				Id:             cluster.ClusterId,
				Provider:       cluster.CloudProviderId.String(),
				Region:         cluster.RegionId.String(),
				ClusterStatus:  cluster.State.String(),
				OrganizationId: cluster.OrganizationId,
				ProjectId:      cluster.ProjectId,
			}, nil
		}
	}

	// active cluster
	cluster, err := s.clusterRepo.GetAndLoadComponents(ctx, request.ClusterId)
	if err != nil {
		return nil, err
	}

	region, err := s.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.OrganizationId)
	if err != nil {
		return nil, err
	}

	clusterRole, err := s.regionServerCli.GetClusterRole(
		ctx,
		region,
		&regionaldedicatedv1.GetClusterRoleRequest{ClusterId: strconv.FormatUint(cluster.ClusterId, 10)},
	)
	if err != nil {
		return nil, err
	}

	topo := &globaldedicatedv1.CompatCluster_ClusterTopology{
		Tidb:    &globaldedicatedv1.CompatCluster_ClusterTopology_Component{},
		Tikv:    &globaldedicatedv1.CompatCluster_ClusterTopology_Component{},
		Pd:      &globaldedicatedv1.CompatCluster_ClusterTopology_Component{},
		Tiflash: &globaldedicatedv1.CompatCluster_ClusterTopology_Component{},
		Tiproxy: &globaldedicatedv1.CompatCluster_ClusterTopology_Component{},
	}

	for _, comp := range cluster.ClusterComponents {
		switch comp.GetComponentType() {
		case globaldedicatedv1.ComponentType_TIDB:
			topo.Tidb.Replicas += int32(comp.GetCount())
			topo.Tidb.InstanceType = comp.GetInstanceType()
		case globaldedicatedv1.ComponentType_TIKV:
			topo.Tikv.Replicas += int32(comp.GetCount())
			topo.Tikv.InstanceType = comp.GetInstanceType()
		case globaldedicatedv1.ComponentType_PD:
			topo.Pd.Replicas += int32(comp.GetCount())
			topo.Pd.InstanceType = comp.GetInstanceType()
		case globaldedicatedv1.ComponentType_TIFLASH:
			topo.Tiflash.Replicas += int32(comp.GetCount())
			topo.Tiflash.InstanceType = comp.GetInstanceType()
		case globaldedicatedv1.ComponentType_TIPROXY:
			topo.Tiproxy.Replicas += int32(comp.GetCount())
			topo.Tiproxy.InstanceType = comp.GetInstanceType()
		}
	}

	// get base info
	compatCluster := &globaldedicatedv1.CompatCluster{
		Id:             cluster.ClusterId,
		Provider:       cluster.CloudProviderId.String(),
		Region:         cluster.RegionId.String(),
		ClusterStatus:  cluster.State.String(),
		OrganizationId: cluster.OrganizationId,
		ProjectId:      cluster.ProjectId,
		NodeRole: &globaldedicatedv1.CompatCluster_NodeRole{
			CloudIamIdentity: clusterRole.CloudIamIdentity,
			ExternalId:       clusterRole.ExternalId,
		},
		ClusterTopology: topo,
	}

	return compatCluster, nil
}

// GetCompatNetwork returns the network information of the specified network.
// It assumes that region+tenant+project uniquely identifies an appbox. If this is not the case, the behavior is undefined.
func (s *dataPlatformCompatServiceImpl) GetCompatNetwork(
	ctx context.Context,
	request *globaldedicatedv1.GetCompatNetworkRequest,
) (*globaldedicatedv1.CompatNetwork, bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "Domain.GetCompatNetwork")
	defer span.End()

	regionName, serr := enum.FromString[globalcommonv1.Region_Name](request.RegionName)
	if serr != nil {
		return nil, bizerr.WrapErrorWithCode(
			serr,
			bizerr.ErrCodeParams,
			"region %s is not supported",
			request.RegionName,
		)
	}

	// get tencent id
	project, err := s.accountCli.GetProject(ctx, request.ProjectId)
	if err != nil {
		return nil, err
	}

	region, err := s.regionRepo.SelectRegionById(ctx, globalcommonv1.Region_Name(regionName), project.TenantID)
	if err != nil {
		return nil, err
	}

	vpcs, err := s.regionServerCli.ListVpcs(ctx, region, &regionaldedicatedv1.ListVpcsRequest{
		TenantId:  strconv.FormatUint(project.TenantID, 10),
		ProjectId: strconv.FormatUint(request.ProjectId, 10),
		ClusterId: "",
		Statuses: []regionaldedicatedv1.Vpc_Status{
			regionaldedicatedv1.Vpc_ACTIVE,
			regionaldedicatedv1.Vpc_CREATING,
			regionaldedicatedv1.Vpc_FAILED,
		},
	})
	if err != nil {
		return nil, err
	}
	if len(vpcs.GetVpcs()) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "network not found")
	}
	vpc := vpcs.GetVpcs()[0]

	appBoxes, err := s.regionServerCli.ListAppBoxes(ctx, region, &regionaldedicatedv1.ListAPPBoxesRequest{
		TenantId:  strconv.FormatUint(project.TenantID, 10),
		ProjectId: strconv.FormatUint(request.ProjectId, 10),
		Statuses: []regionaldedicatedv1.APPBox_Status{
			regionaldedicatedv1.APPBox_CREATING,
			regionaldedicatedv1.APPBox_ACTIVE,
			regionaldedicatedv1.APPBox_FAILED,
		},
	})
	if err != nil {
		return nil, err
	}
	if len(appBoxes.GetAppboxes()) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "app box not found")
	}
	appBox := appBoxes.GetAppboxes()[0]

	compatNetwork := &globaldedicatedv1.CompatNetwork{
		K8SName:      appBox.GetKubernetes().GetName(),
		K8SVersion:   appBox.GetKubernetes().GetVersion(),
		K8SCa:        appBox.GetKubernetes().GetCa(),
		K8SEndpoint:  appBox.GetKubernetes().GetEndpoint(),
		K8SReady:     appBox.GetStatus() == regionaldedicatedv1.APPBox_ACTIVE,
		ProxyBaseUrl: appBox.GetProxyBaseUrl(),
		VpcId:        vpc.GetVpcId(),
		Iam: &globaldedicatedv1.CompatNetwork_IAM{
			AccountId: appBox.GetKubernetes().GetAccountId(),
			// Deprecated: Use GetClusterRole api instead.
			ExternalId: appBox.Kubernetes.GetExternalId(),
		},
		Cidr:             vpc.GetCidrBlock(),
		DataflowAgentUrl: appBox.GetDataflowAgentUrl(),
		ExternalIps:      vpc.GetExternalIps(),
		TenantId:         strconv.FormatUint(project.TenantID, 10),
	}

	switch info := appBox.GetKubernetes().GetInfo().(type) {
	case *regionaldedicatedv1.APPBox_Kubernetes_Aws_:
		awsNetwork := &globaldedicatedv1.CompatNetwork_Aws{
			SecurityGroupId: info.Aws.GetSecurityGroup(),
		}

		for _, subnet := range info.Aws.GetNodeSubnets() {
			awsNetwork.NodeSubnets = append(awsNetwork.NodeSubnets,
				&globaldedicatedv1.CompatNetwork_Aws_NodeSubnet{
					SubnetId:           subnet.GetId(),
					AvailabilityZone:   subnet.GetAzName(),
					AvailabilityZoneId: subnet.GetAzId(),
				},
			)
		}
		for _, subnet := range info.Aws.GetPrivateLoadbalancerSubnets() {
			awsNetwork.PrivateSubnets = append(awsNetwork.PrivateSubnets,
				&globaldedicatedv1.CompatNetwork_Aws_NodeSubnet{
					SubnetId:           subnet.GetId(),
					AvailabilityZone:   subnet.GetAzName(),
					AvailabilityZoneId: subnet.GetAzId(),
				},
			)
		}
		for _, subnet := range info.Aws.GetSecondaryNodeSubnets() {
			awsNetwork.SecondaryNodeSubnets = append(awsNetwork.SecondaryNodeSubnets,
				&globaldedicatedv1.CompatNetwork_Aws_NodeSubnet{
					SubnetId:           subnet.GetId(),
					AvailabilityZone:   subnet.GetAzName(),
					AvailabilityZoneId: subnet.GetAzId(),
				},
			)
		}

		compatNetwork.Info = &globaldedicatedv1.CompatNetwork_Aws_{
			Aws: awsNetwork,
		}
		compatNetwork.NodeRole = info.Aws.GetAdminNodeRole()

	case *regionaldedicatedv1.APPBox_Kubernetes_Gcp_:
		gcpNetwork := &globaldedicatedv1.CompatNetwork_Gcp{
			NodeZones:   info.Gcp.GetNodeZones(),
			PscSubnet:   info.Gcp.GetPscSubnet(),
			AdminNodeSa: info.Gcp.GetAdminNodeSa(),
			NodeSubnets: lo.Map(
				info.Gcp.GetNodeSubnets(),
				func(i *regionaldedicatedv1.APPBox_Kubernetes_GcpSubnet, _ int) string { return i.GetId() },
			),
		}

		compatNetwork.Info = &globaldedicatedv1.CompatNetwork_Gcp_{
			Gcp: gcpNetwork,
		}
		compatNetwork.NodeRole = info.Gcp.GetAdminNodeSa()

	case *regionaldedicatedv1.APPBox_Kubernetes_Azure_:
		azureNetwork := &globaldedicatedv1.CompatNetwork_Azure{
			NodeZones: info.Azure.GetNodeZones(),
			NodeSubnetIds: lo.Map(
				info.Azure.GetNodeSubnets(),
				func(i *regionaldedicatedv1.APPBox_Kubernetes_AzureSubnet, _ int) string { return i.GetId() },
			),
			PodSubnetIds: lo.Map(
				info.Azure.GetPodSubnets(),
				func(i *regionaldedicatedv1.APPBox_Kubernetes_AzureSubnet, _ int) string { return i.GetId() },
			),
			ResourceGroup: info.Azure.GetResourceGroup(),
		}

		compatNetwork.Info = &globaldedicatedv1.CompatNetwork_Azure_{
			Azure: azureNetwork,
		}
	}

	return compatNetwork, nil
}

func (s *dataPlatformCompatServiceImpl) GetCompatProvider(
	ctx context.Context,
	request *globaldedicatedv1.GetCompatProviderRequest,
) (*globaldedicatedv1.CompatProvider, bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "Domain.GetCompatProvider")
	defer span.End()

	// There are no more eks/gke provider types in the new architecture,
	// only cloud providers (aws/gcp)
	// However, there are four infra provider types in the old architecture, aws/gcp/eks/gke,
	// eks in the old architecture is equivalent to aws in the new architecture.
	// gke in the old architecture is equivalent to gcp in the new architecture.
	// aws/gcp in the old architecture will ignore
	var cloudProviderName globalcommonv1.CloudProvider_Name
	switch request.ProviderName {
	case "eks":
		cloudProviderName = globalcommonv1.CloudProvider_AWS
	case "gke":
		cloudProviderName = globalcommonv1.CloudProvider_GCP
	case "aks":
		cloudProviderName = globalcommonv1.CloudProvider_AZURE
	case "aws", "gcp", "azure":
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"new architecture does not support infra provider %s ",
			request.ProviderName,
		)
	}

	provider, err := s.regionRepo.GetCloudProviderById(ctx, cloudProviderName, 0)
	if err != nil {
		return nil, err
	}

	compatProvider := &globaldedicatedv1.CompatProvider{Name: provider.Id.String()}

	for _, region := range provider.Regions {
		config, err := s.regionServerCli.GetRegionConfig(ctx, region)
		if err != nil {
			return nil, err
		}

		compatProvider.Regions = append(compatProvider.Regions,
			&globaldedicatedv1.CompatProvider_CompatRegion{
				Name:  region.LogicalName,
				Zones: config.Zones,
			},
		)
	}

	return compatProvider, nil
}
