package server

import (
	"context"
	"fmt"
	"sort"

	"github.com/AlekSi/pointer"
	"github.com/tidbcloud/pkgv2/bizerr"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	config2 "github.com/tidbcloud/cluster-controller/server/pkg/config"
	"github.com/tidbcloud/cluster-controller/server/server/cvt"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

const (
	GiB                                = 1024 * 1024 * 1024
	TiKVDedicatedRaftEngineStorageSize = "50Gi"
)

var TiKVDedicatedRaftEngineStorageClass = map[string]string{
	cloudv1.ProviderTypeAWS: "tikv-gp3-3000-125",
	cloudv1.ProviderTypeEKS: "tikv-gp3-3000-125",
}

func genNamespace(prefix, projectID string) string {
	return fmt.Sprintf("%s%s", prefix, projectID)
}

func getAwsSubnetFromReq(reqSubnet []*apiv1.Kubernetes_AwsSubnet) []cloudv1.AWSSubnet {
	crSubnet := make([]cloudv1.AWSSubnet, 0, len(reqSubnet))
	for _, subnet := range reqSubnet {
		crSubnet = append(crSubnet, cloudv1.AWSSubnet{
			SubnetID: subnet.Id,
			Zone:     subnet.AzName,
		})
	}
	return crSubnet
}

func authPubKeySecretName(clusterId string) string {
	return fmt.Sprintf("%s-tidb-auth-token-jwks-secret", clusterId)
}

func getInfraProviderFromReq(clusterReq *apiv1.Cluster) string {
	if clusterReq.Kubernetes.GetAws() != nil {
		return cloudv1.ProviderTypeEKS
	}
	if clusterReq.Kubernetes.GetGcp() != nil {
		return cloudv1.ProviderTypeGKE
	}
	if clusterReq.Kubernetes.GetAzure() != nil {
		return cloudv1.ProviderTypeAKS
	}
	// unknown cloud privider type
	return ""
}

func storageSizeGi2quantity(sizeGi uint32) resource.Quantity {
	sizeInt64 := int64(sizeGi)
	q := resource.NewQuantity(sizeInt64*GiB, resource.BinarySI)
	_ = q.String()
	return *q
}

func getStorageClassNameFromComponentReq(component *apiv1.Cluster_NodeSpec) string {
	if component.Storage == nil {
		return ""
	}

	return cvt.InfraStorageClassName(component.ComponentType, component.Storage.Type, int32(component.Storage.Iops), int32(component.Storage.ThroughputMbs))
}

func getAdditionalStorageVolumesFromComponentReq(component *apiv1.Cluster_NodeSpec) []cloudv1.ComponentStorageVolume {
	volumes := make([]cloudv1.ComponentStorageVolume, 0, len(component.AdditionalStorage))
	for _, vol := range component.AdditionalStorage {
		storageClassName := cvt.InfraStorageClassName(component.ComponentType, vol.Type, int32(vol.Iops), int32(vol.ThroughputMbs))
		volumes = append(volumes, cloudv1.ComponentStorageVolume{
			Name:             vol.Name,
			StorageClassName: storageClassName,
			StorageSize:      storageSizeGi2quantity(vol.SizeGi),
			MountPath:        vol.MountPath,
		})
	}
	sort.Slice(volumes, func(i, j int) bool {
		return volumes[i].Name < volumes[j].Name
	})
	return volumes
}

// nolint: unused
func setClusterForTiKVDedicatedRaftEngine(providerType string, clusterCr *cloudv1.Cluster) bizerr.BizError {
	sc, exist := TiKVDedicatedRaftEngineStorageClass[providerType]
	if exist {
		clusterCr.Spec.Cluster.TiKV.DedicatedRaftEngineStorage = &cloudv1.TiKVDedicatedRaftEngineStorage{
			ComponentStorageSpec: cloudv1.ComponentStorageSpec{
				StorageSize:      resource.MustParse(TiKVDedicatedRaftEngineStorageSize),
				StorageClassName: pointer.ToString(sc),
			},
		}
	}

	return nil
}

// buildClusterLabelsForCreating add the labels need when create the cluster.
// If the cluster uses dev tier, do not add `tenant` and `project` billing labels.
func buildClusterLabelsForCreating(infraProviderType, region, tid, pid, cid string) map[string]string {
	labels := make(map[string]string)
	labels[cloudv1.TenantTagKey] = tid
	labels[cloudv1.ProjectTagKey] = pid
	labels[cloudv1.ClusterTagKey] = cid
	labels[cloudv1.LabelServiceType] = cloudv1.ServiceTypeDedicated
	labels[cloudv1.ProviderTypeLabel] = infraProviderType
	labels[cloudv1.RegionLabel] = region
	labels[consts.ClusterServerCreatorLabelKey] = consts.ClusterServerCreatorLabelValue

	return labels
}

func buildNamespaceLabelsForCreating(tid, pid string) map[string]string {
	labels := make(map[string]string)
	labels[cloudv1.TenantTagKey] = tid
	labels[cloudv1.ProjectTagKey] = pid
	labels[consts.ClusterServerCreatorLabelKey] = consts.ClusterServerCreatorLabelValue

	return labels
}

func ensureNamespace(ctx context.Context, cli client.Client, ns, tid, pid string) error {
	namespace := &corev1.Namespace{}
	err := cli.Get(ctx, client.ObjectKey{Name: ns}, namespace)
	if err != nil {
		if apierrors.IsNotFound(err) {
			return cli.Create(ctx, &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name:   ns,
					Labels: buildNamespaceLabelsForCreating(tid, pid),
				},
			})
		}
		return err
	}
	return nil
}

func buildJWKsSecret(c *cloudv1.Cluster, jwks string) *corev1.Secret {
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: c.Namespace,
			Name:      c.GetJWKsSecretName(),
			Labels: map[string]string{
				cloudv1.LabelUsedFor:     cloudv1.UsedForTokenBasedAuth,
				cloudv1.LabelServiceType: cloudv1.ServiceTypeDedicated,
				cloudv1.TenantTagKey:     c.GetTenantID(),
				cloudv1.ProjectTagKey:    c.GetProjectID(),
				cloudv1.ClusterTagKey:    c.GetClusterID(),
			},
		},
		Data: map[string][]byte{
			cloudv1.TiDBAuthTokenJWKSFilename: []byte(jwks),
		},
	}
	return secret
}

func initClusterFeatures(cluster *cloudv1.Cluster) {
	if config2.GetConfig().UseSelfSignedCertForTiDBComponents {
		cluster.EnableFeature(consts.ClusterFeatureUseSelfSignedCertForTiDBComponents)
	}
	if config2.GetConfig().ShortLivedComponentCert {
		cluster.EnableFeature(consts.ClusterFeatureShortLivedComponentCert)
	}
	// Using tiproxy on a cluster with self-signed certificate feature enabled depends on the new cert layout v1 of tiproxy.
	// Tiproxy cert layout v1 is compatible with both self-signed cert and PCA-signed cert.
	cluster.EnableFeature(consts.ClusterFeatureTiProxyCertLayoutV1)

	// enable the following features by default. To remove them until they are fully enabeld for all.
	cluster.EnableFeature(consts.ClusterFeatureTiDBExposeReplayerPath)
	cluster.EnableFeature(consts.ClusterFeatureTiDBExposeOOMPath)
	cluster.EnableFeature(consts.ClusterFeatureDedicatedSG)
	cluster.EnableFeature(consts.ClusterFeatureGCP3LayerNodePool)
	cluster.EnableFeature(consts.ClusterFeatureEnableTiFlashGracefulShutdown)
	cluster.EnableFeature(consts.ClusterFeatureEnableTiFlashGracefulShutdownSleep)
}
