package cluster

import (
	"fmt"
	"time"

	kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types"
	awskmsv1alpha1 "github.com/crossplane/provider-aws/apis/kms/v1alpha1"
	awsdnsv1alpha1 "github.com/crossplane/provider-aws/apis/route53/v1alpha1"
	cmv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	"github.com/pingcap/errors"
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	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/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"
	"github.com/tidbcloud/shared-provider/pkg/utils"
)

const (
	crossPlaneFinalizer = "finalizer.managedresource.crossplane.io"

	deleteKeyWaitInterval = 2 * time.Second
)

func (r *reconcileContext) pendingDeleteCluster() error {
	// delete all not finished backups
	if names, err := r.getNotFinishedBackupNames(); err != nil {
		return err
	} else if len(names) > 0 {
		if err := r.deleteBackups(names); err != nil {
			return err
		} else {
			return errors.Errorf("wait for backups %v to be removed", names)
		}
	}

	// delete all restores
	if names, err := r.getRestoreNames(); err != nil {
		return err
	} else if len(names) > 0 {
		if err := r.deleteRestores(names); err != nil {
			return err
		} else {
			return errors.Errorf("wait for restores %v to be removed", names)
		}
	}

	// delete all imports
	if names, err := r.getImportNames(); err != nil {
		return err
	} else if len(names) > 0 {
		if err := r.deleteImports(names); err != nil {
			return err
		} else {
			return errors.Errorf("wait for imports %v to be removed", names)
		}
	}

	return r.updateProgress(cloudv1.ClusterPhaseDeleting, "cluster is being deleted")
}

func (r *reconcileContext) getNotFinishedBackupNames() ([]string, error) {
	var backupList cloudv1.BackupList
	if err := r.InfraClient.List(r.ctx, &backupList, client.InNamespace(r.cluster.Namespace),
		client.MatchingFields(map[string]string{
			"spec.clusterName": r.cluster.Name,
		})); err != nil {
		return nil, err
	}
	var names []string
	for _, item := range backupList.Items {
		if item.Status.Phase != cloudv1.BackupComplete && item.Status.Phase != cloudv1.BackupFailed {
			names = append(names, item.Name)
		}
	}
	return names, nil
}

func (r *reconcileContext) getRestoreNames() ([]string, error) {
	var restoreList cloudv1.RestoreList
	if err := r.InfraClient.List(r.ctx, &restoreList, client.InNamespace(r.cluster.Namespace),
		client.MatchingFields(map[string]string{
			"spec.clusterName": r.cluster.Name,
		})); err != nil {
		return nil, err
	}
	var names []string
	for _, item := range restoreList.Items {
		names = append(names, item.Name)
	}
	return names, nil
}

func (r *reconcileContext) getImportNames() ([]string, error) {
	var importList cloudv1.ImportList
	if err := r.InfraClient.List(r.ctx, &importList, client.InNamespace(r.cluster.Namespace),
		client.MatchingFields(map[string]string{
			"spec.clusterName": r.cluster.Name,
		})); err != nil {
		return nil, err
	}
	var names []string
	for _, item := range importList.Items {
		names = append(names, item.Name)
	}
	return names, nil
}

func (r *reconcileContext) deleteBackups(names []string) error {
	// most of the time there is usually only one Backup, so no need to delete them concurrently
	for _, name := range names {
		if err := r.InfraClient.Delete(r.ctx, &cloudv1.Backup{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: r.cluster.Namespace,
			},
		}); client.IgnoreNotFound(err) != nil {
			return err
		}
	}
	return nil
}

func (r *reconcileContext) deleteRestores(names []string) error {
	// most of the time there is usually only one Restore, so no need to delete them concurrently
	for _, name := range names {
		if err := r.InfraClient.Delete(r.ctx, &cloudv1.Restore{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: r.cluster.Namespace,
			},
		}); client.IgnoreNotFound(err) != nil {
			return err
		}
	}
	return nil
}

func (r *reconcileContext) deleteImports(names []string) error {
	// most of the time there is usually only one Import, so no need to delete them concurrently
	for _, name := range names {
		if err := r.InfraClient.Delete(r.ctx, &cloudv1.Import{
			ObjectMeta: metav1.ObjectMeta{
				Name:      name,
				Namespace: r.cluster.Namespace,
			},
		}); client.IgnoreNotFound(err) != nil {
			return err
		}
	}
	return nil
}

func (r *reconcileContext) deleteCluster() error {
	if err := r.deleteInitializer(); err != nil {
		return err
	}
	if err := r.deletePrivateLink(); err != nil {
		return err
	}
	if err := r.deletePublicEndpoint(true); err != nil {
		return err
	}
	if err := r.deleteTidbMonitor(); err != nil {
		return err
	}
	if err := r.deleteTidbCluster(); err != nil {
		return err
	}
	if err := r.deleteKmsKey(); err != nil {
		return err
	}
	if err := r.deleteCertTLS(); err != nil {
		return err
	}
	if err := r.deletePVC(); err != nil {
		return err
	}

	return nil
}

func (r *reconcileContext) deleteCertTLS() error {
	serverCert := &cmv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tidbServerSecretName(r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
	}
	if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, serverCert)); err != nil {
		return err
	}

	clientCert := &cmv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      clusterClientSecretName(r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
	}
	if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, clientCert)); err != nil {
		return err
	}

	for _, comp := range r.cluster.GetComponents() {
		compCert := &cmv1.Certificate{
			ObjectMeta: metav1.ObjectMeta{
				Name:      tidbClusterComponentSecretName(r.tcKey.Name, comp.GetComponentType()),
				Namespace: r.tcKey.Namespace,
			},
		}
		if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, compCert)); err != nil {
			return err
		}
	}

	return nil
}

func (r *reconcileContext) deleteInitializer() error {
	ti := &v1alpha1.TidbInitializer{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.tiKey.Namespace,
			Name:      r.tiKey.Name,
		},
	}
	return client.IgnoreNotFound(r.Client.Delete(r.ctx, ti))
}

func (r *reconcileContext) deletePublicEndpoint(force bool) error {
	var err error

	publicRecord := &awsdnsv1alpha1.ResourceRecordSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: r.publicDnsRecordName(),
		},
	}
	if force {
		if err = client.IgnoreNotFound(r.InfraClient.Delete(r.ctx, publicRecord)); err != nil {
			return err
		}
	} else {
		if err = r.InfraCache.Get(r.ctx, client.ObjectKeyFromObject(publicRecord), publicRecord); err == nil {
			if err = client.IgnoreNotFound(r.InfraClient.Delete(r.ctx, publicRecord)); err != nil {
				return err
			}
		} else if !apierrors.IsNotFound(err) {
			return err
		}
	}

	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      getPublicServiceName(r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
	}
	if force {
		if err = client.IgnoreNotFound(r.Client.Delete(r.ctx, svc)); err != nil {
			return err
		}
	} else {
		if err = r.Cache.Get(r.ctx, client.ObjectKeyFromObject(svc), svc); err == nil {
			if err = client.IgnoreNotFound(r.Client.Delete(r.ctx, svc)); err != nil {
				return err
			}
		} else if !apierrors.IsNotFound(err) {
			return err
		}
	}

	tidbAccessPolicy := &netv1.NetworkPolicy{
		ObjectMeta: metav1.ObjectMeta{
			Name:      getTiDBWhiteListPolicyName(r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
	}
	if force {
		if err = client.IgnoreNotFound(r.Client.Delete(r.ctx, tidbAccessPolicy)); err != nil {
			return err
		}
	} else {
		if err = r.Cache.Get(r.ctx, client.ObjectKeyFromObject(tidbAccessPolicy), tidbAccessPolicy); err == nil {
			if err = client.IgnoreNotFound(r.Client.Delete(r.ctx, tidbAccessPolicy)); err != nil {
				return err
			}
		} else if !apierrors.IsNotFound(err) {
			return err
		}
	}

	return nil
}

func (r *reconcileContext) deleteKmsKey() error {
	keyName := r.cluster.ClusterScopeName()
	k := &awskmsv1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name: keyName,
		},
	}
	if err := r.Client.Delete(r.ctx, k); err != nil {
		if apierrors.IsNotFound(err) {
			return nil
		}
		return err
	}

	// delete key in crossplane will be in Pending deletion, fore delete key by remove crossPlaneFinalizer
	if err := r.Client.Get(r.ctx, client.ObjectKey{Name: keyName}, k); err != nil {
		if apierrors.IsNotFound(err) {
			return nil
		}
		return err
	}

	if k.Status.AtProvider.KeyState == nil || *k.Status.AtProvider.KeyState != string(kmstypes.KeyStatePendingDeletion) {
		r.V(4).Info("waiting keystate to be PendingDeletion")
		return &utils.RequeueAfterError{Cause: nil, RequeueAfter: deleteKeyWaitInterval}
	}

	controllerutil.RemoveFinalizer(k, crossPlaneFinalizer)

	return r.Client.Update(r.ctx, k)
}

func (r *reconcileContext) deleteTidbCluster() error {
	toDeleteTc := &v1alpha1.TidbCluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      r.tcKey.Name,
			Namespace: r.tcKey.Namespace,
		},
	}
	if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, toDeleteTc)); err != nil {
		return err
	}

	toDeleteSecret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-dashboard-session-secret", r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
	}

	return client.IgnoreNotFound(r.Client.Delete(r.ctx, toDeleteSecret))
}

func (r *reconcileContext) deleteTidbMonitor() error {
	toDelete := &v1alpha1.TidbMonitor{
		ObjectMeta: metav1.ObjectMeta{
			Name:      r.tmKey.Name,
			Namespace: r.tmKey.Namespace,
		},
	}

	return client.IgnoreNotFound(r.Client.Delete(r.ctx, toDelete))
}

// deletePVC delete tc/tm PVC(there instance name are same).
func (r *reconcileContext) deletePVC() error {
	// TODO: use versioned github.com/pingcap/tidb-operator
	// selector, err := label.New().Instance(r.tcKey.Name).Selector()
	selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: map[string]string{
		label.NameLabelKey:      "tidb-cluster",
		label.ManagedByLabelKey: "tidb-operator",
		label.InstanceLabelKey:  r.tcKey.Name,
	}})
	if err != nil {
		return fmt.Errorf("failed to create label selector err: %w", err)
	}

	err = r.Client.DeleteAllOf(r.ctx, &corev1.PersistentVolumeClaim{}, &client.DeleteAllOfOptions{
		ListOptions: client.ListOptions{Namespace: r.tcKey.Namespace, LabelSelector: selector},
	})
	if client.IgnoreNotFound(err) != nil {
		return err
	}

	return nil
}
