package gcp

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

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

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
)

const (
	defaultProvisioner = "pd.csi.storage.gke.io"
)

var (
	bindingMode   = storagev1.VolumeBindingWaitForFirstConsumer
	reclaimPolicy = corev1.PersistentVolumeReclaimDelete
)

type StorageClassTask struct {
	*reconciler.BaseTask[*agentv2.O11yK8sAgent]
}

func NewStorageClassTask(ctx *reconciler.Context[*agentv2.O11yK8sAgent]) *StorageClassTask {
	return &StorageClassTask{
		BaseTask: &reconciler.BaseTask[*agentv2.O11yK8sAgent]{Context: ctx},
	}
}

func (t *StorageClassTask) Name() string {
	return "StorageClass"
}

func (t *StorageClassTask) Create(ctx context.Context) deploy.Result {
	tags := map[string]string{
		consts.TagKeyBillingServiceType: "o11y",
		consts.TagKeyBillingTenant:      t.Agent.Spec.O11y.TenantID,
		consts.TagKeyBillingProject:     t.Agent.Spec.O11y.ProjectID,
		consts.TagKeyBillingUsedBy:      "o11y-agent",
	}
	labels := []string{}
	for k, v := range tags {
		labels = append(labels, fmt.Sprintf("%s=%s", k, v))
	}
	sc := &storagev1.StorageClass{
		// Infra API defaults an empty storage class to cloud-ssd, and the provider spec
		// will not include the cloud-ssd storage class.
		ObjectMeta: metav1.ObjectMeta{
			Name: consts.MetricsAgentStorageClassName,
		},
		VolumeBindingMode:    &bindingMode,
		AllowVolumeExpansion: ptr.To(true),
		ReclaimPolicy:        &reclaimPolicy,
		Provisioner:          defaultProvisioner,
		Parameters: map[string]string{
			"csi.storage.k8s.io/fstype": "ext4",
			"type":                      consts.GCPVolumeTypePDSSD,
			"labels":                    strings.Join(labels, ","),
		},
		MountOptions: []string{"nodelalloc,noatime"},
	}
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	oldSc := &storagev1.StorageClass{}
	err = cli.Get(ctx, client.ObjectKey{Name: consts.MetricsAgentStorageClassName}, oldSc)
	if err != nil && !apierrors.IsNotFound(err) {
		return deploy.Panic(err)
	}
	if apierrors.IsNotFound(err) {
		// create storageclass
		_, err = controllerutil.CreateOrUpdate(ctx, cli, sc, func() error {
			return nil
		})
		if err != nil {
			return deploy.Panic(err)
		}
	} else {
		// delete dirty storageclass, since gke does not support "/" in tags parameter
		// refer to https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing#creating_tag
		if strings.Contains(oldSc.Parameters["labels"], "tags.tidbcloud.com/tenant") {
			err = cli.Delete(ctx, oldSc)
			if err != nil {
				return deploy.Panic(err)
			}
			// requeue
			return deploy.Panic(errors.New("waiting for storageclass to be deleted")).WithRequeueAfter(time.Second * 5)
		}
	}
	return deploy.Done()
}
