package common

import (
	"context"
	"fmt"

	opv2 "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

const (
	// Copied from infra-api/api/v1/cluster_helpers.go
	tidbNamespaceFormat = "tidb%s"

	// See https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/pod_readiness_gate/
	labelKeyNLBPodReadinessGateInject        = "elbv2.k8s.aws/pod-readiness-gate-inject"
	labelValNLBPodReadinessGateInjectEnabled = "enabled"

	// https://github.com/pingcap/tidb-operator/blob/f21fe790a5b794ebbce35fc7196ea77429ec9236/pkg/apis/label/label.go#L68
	operatorv1BackupFinalizer = "tidb.pingcap.com/backup-protection"
	// https://github.com/pingcap/tidb-operator/blob/feature/v2/api/meta/v1alpha1/types.go#L19
	operatorv2BackupFinalizer = "pingcap.com/finalizer"
)

// Namespace deploys the namespace for the TiDB cluster.
type Namespace struct {
	*task.BaseTask
	namespaces []string
}

func NewNamespace(ctx *task.Context) *Namespace {
	namespace := fmt.Sprintf(tidbNamespaceFormat, ctx.Cluster.Name)
	return &Namespace{
		BaseTask: &task.BaseTask{Context: ctx},
		namespaces: []string{
			namespace,
		},
	}
}

func (t *Namespace) Name() string {
	return "Namespace"
}

func (t *Namespace) Create(ctx context.Context) deploy.Result {
	cli := t.EnvProvider.GetClient()

	for _, namespace := range t.namespaces {
		ns := &corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				Name: namespace,
				Labels: map[string]string{
					labelKeyNLBPodReadinessGateInject: labelValNLBPodReadinessGateInjectEnabled,
				},
			},
		}
		_, err := controllerutil.CreateOrUpdate(ctx, cli, ns, func() error { return nil })
		// We only create one namespace, and this namespace is crucial to following tasks
		// So when namespace create failed, we don't need to do postcreate check and other tasks
		if err != nil {
			return deploy.Panic(err)
		}
	}
	return deploy.Done()
}

func (t *Namespace) Delete(ctx context.Context) deploy.Result {
	cli := t.EnvProvider.GetClient()
	for _, namespace := range t.namespaces {
		// clean up backup CR in the namespace
		if err := t.cleanUpBackupCR(ctx, namespace); err != nil {
			return deploy.Panic(err)
		}

		ns := &corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				Name: namespace,
			},
		}
		if err := k8s.DeleteObjectCompleted(ctx, cli, ns); err != nil {
			return deploy.Panic(err)
		}
	}
	return deploy.Done()
}

func (t *Namespace) PostCreate(ctx context.Context) deploy.Result {
	// we don't need to do PostCreate check as the result is already indicated by above Create function
	return deploy.Done()
}

func (t *Namespace) cleanUpBackupCR(ctx context.Context, namespace string) error {
	cli := t.EnvProvider.GetClient()

	// refer to https://docs.pingcap.com/tidb-in-kubernetes/stable/backup-restore-overview/#delete-the-backup-cr
	if t.Cluster.FeatureEnabled(consts.ClusterFeatureUseTiDBOperatorV2) {
		opv2Backup := &opv2.BackupList{}
		if err := cli.List(ctx, opv2Backup, client.InNamespace(namespace)); err != nil {
			return err
		}

		for i := range opv2Backup.Items {
			controllerutil.RemoveFinalizer(&opv2Backup.Items[i], operatorv2BackupFinalizer)
			if err := cli.Update(ctx, &opv2Backup.Items[i]); err != nil {
				return err
			}
		}
	} else {
		opv1Backup := &opv1alpha1.BackupList{}
		if err := cli.List(ctx, opv1Backup, client.InNamespace(namespace)); err != nil {
			return err
		}

		for i := range opv1Backup.Items {
			controllerutil.RemoveFinalizer(&opv1Backup.Items[i], operatorv1BackupFinalizer)
			if err := cli.Update(ctx, &opv1Backup.Items[i]); err != nil {
				return err
			}
		}
	}
	return nil
}
