package gardener

import (
	"context"

	"github.com/pingcap/errors"
	"k8s.io/utils/pointer"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/k8s"

	corev1 "k8s.io/api/core/v1"
	storagev1 "k8s.io/api/storage/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-api/controllers/provider/gardener/version"
)

const (
	operatorImage           = "gcr.io/pingcap-public/dbaas/tidb-operator:v1.5.0-alpha.8"
	backupManagerImage      = "gcr.io/pingcap-public/dbaas/tidb-backup-manager:v1.5.0-alpha.8"
	advanceStatefulsetImage = "gcr.io/pingcap-public/dbaas/advanced-statefulset:v0.5.0"

	// shootClusterOperatorRole is the ClusterRole of the human operator of shoots
	shootClusterOperatorRole = "dbaas:shoot-operator"
)

// ensureStorageClass setup storage classes for target shoot cluster
func ensureStorageClasses(ctx context.Context, cli client.Client, providerType string, k8sVersion string) error {
	waitForFirstConsumer := storagev1.VolumeBindingWaitForFirstConsumer
	reclaimDelete := corev1.PersistentVolumeReclaimDelete
	remoteStorageClass := &storagev1.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: k8s.DefaultStorageClassName,
		},
		VolumeBindingMode:    &waitForFirstConsumer,
		AllowVolumeExpansion: pointer.Bool(true),
		ReclaimPolicy:        &reclaimDelete,
		MountOptions:         []string{"nodelalloc,noatime"},
	}

	switch providerType {
	case consts.ProviderTypeAWS:
		remoteStorageClass.Provisioner = "kubernetes.io/aws-ebs"
		remoteStorageClass.Parameters = map[string]string{
			"type":   "gp2",
			"fsType": "ext4",
		}

	case consts.ProviderTypeGCP:
		remoteStorageClass.Provisioner = "kubernetes.io/gce-pd"
		remoteStorageClass.Parameters = map[string]string{
			"type": "pd-ssd",
		}
	default:
		return errors.Errorf("unknown provider type%s", providerType)
	}

	_, err := k8s.CreateIgnoreExists(ctx, cli, remoteStorageClass)
	if err != nil {
		return err
	}

	localStorageClass := &storagev1.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: "local-storage",
		},
		Provisioner:       "kubernetes.io/no-provisioner",
		VolumeBindingMode: &waitForFirstConsumer,
		MountOptions:      []string{"nodelalloc,noatime"},
	}

	_, err = k8s.CreateIgnoreExists(ctx, cli, localStorageClass)
	if err != nil {
		return err
	}

	if providerType == consts.ProviderTypeAWS {
		k8sVersionLessThan118, err := version.CompareVersions(k8sVersion, "<", "1.18")
		if err != nil {
			return err
		}

		if !k8sVersionLessThan118 {
			err = ensureGP3StorageClasses(ctx, cli)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func ensureGP3StorageClasses(ctx context.Context, cli client.Client) error {
	if err := ensureGP3StorageClass(ctx, cli, k8s.PDGp3StorageClassName, k8s.PDGp3IOPS, k8s.PDGp3ThroughputMBps); err != nil {
		return err
	}
	if err := ensureGP3StorageClass(ctx, cli, k8s.TiKVGp3StorageClassName, k8s.TiKVGp3IOPS, k8s.TiKVGp3ThroughputMBps); err != nil {
		return err
	}
	if err := ensureGP3StorageClass(ctx, cli, k8s.TiCDCGp3StorageClassName, k8s.TiCDCGp3IOPS, k8s.TiCDCGp3ThroughputMBps); err != nil {
		return err
	}
	if err := ensureGP3StorageClass(ctx, cli, k8s.ImportGp3StorageClassName, k8s.ImportGp3IOPS, k8s.ImportGp3ThroughputMBps); err != nil {
		return err
	}

	return ensureGP3StorageClass(ctx, cli, k8s.TiFlashGp3StorageClassName, k8s.TiFlashGp3IOPS, k8s.TiFlashGp3ThroughputMBps)
}

func ensureGP3StorageClass(ctx context.Context, cli client.Client, name string, iops string, throughput string) error {
	return cloudv1.EnsureAWSStorageClass(ctx, cli, name, map[string]string{
		"type":       "gp3",
		"iops":       iops,
		"throughput": throughput,
	})
}
