package cluster

import (
	"context"
	"fmt"

	awskmsv1alpha1 "github.com/crossplane/provider-aws/apis/kms/v1alpha1"
	awsdnsv1alpha1 "github.com/crossplane/provider-aws/apis/route53/v1alpha1"
	gcpdnsv1alpha1 "github.com/crossplane/provider-gcp/apis/dns/v1alpha1"
	"github.com/pingcap/errors"
	"go.uber.org/multierr"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	reconcileerr "github.com/tidbcloud/infra-api/controllers/common/error"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"

	"github.com/tidbcloud/infra-api/pkg/nodegroup"

	"github.com/tidbcloud/infra-api/pkg/gardener"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"

	"github.com/tidbcloud/infra-api/pkg/consts"
)

func (r *reconcileContext) deleteEndpoints() error {
	var errs error
	if err := r.deletePublicEndpoints(); err != nil {
		errs = multierr.Append(errs, err)
	}
	if err := r.deletePrivateEndpoints(); err != nil {
		errs = multierr.Append(errs, err)
	}
	return errs
}

func DeleteAWSResourceRecordSet(ctx context.Context, cli client.Client, name string) error {
	publicRecord := &awsdnsv1alpha1.ResourceRecordSet{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if err := client.IgnoreNotFound(cli.Delete(ctx, publicRecord)); err != nil {
		return err
	}

	return nil
}

func (r *reconcileContext) deletePublicEndpoints() error {
	// NB: we should cleanup all possible dns records since the spec or status may not reflect the current reality
	switch r.ProviderType {
	case consts.ProviderTypeAWS:
		// cleanup public if exists
		return DeleteAWSResourceRecordSet(r.ctx, r.Client, r.publicDnsRecordName())
	case consts.ProviderTypeGCP:
		if isUseLegacyDNS(r.Target) {
			// cleanup public if exists
			publicRecord := &gcpdnsv1alpha1.ResourceRecordSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: r.publicDnsRecordName(),
				},
			}
			if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, publicRecord)); err != nil {
				return err
			}
		} else {
			publicRecord := &awsdnsv1alpha1.ResourceRecordSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: r.publicDnsRecordName(),
				},
			}
			if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, publicRecord)); err != nil {
				return err
			}

		}
	default:
		return fmt.Errorf("unsupported provider %s", r.ProviderType)
	}
	return nil
}

func (r *reconcileContext) deleteInitializer() error {
	name := getInitializerName(consts.ClusterPrefix)
	init := v1alpha1.TidbInitializer{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.Target.PrefixedId(),
			Name:      name,
		},
	}
	return client.IgnoreNotFound(r.shootClient.Delete(r.ctx, &init))
}

func (r *reconcileContext) deletePrivateEndpoints() error {
	// NB: we should cleanup all possible dns records since the spec or status may not reflect the current reality
	svcName := getPrivateServiceName(consts.ClusterPrefix)
	svcNs := r.Target.PrefixedId()
	err := r.shootClient.Delete(r.ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{
		Name:      svcName,
		Namespace: svcNs,
	}})
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.AddStack(err)
	}
	switch r.ProviderType {
	case consts.ProviderTypeAWS:
		// cleanup private if exists
		privateRecord := &awsdnsv1alpha1.ResourceRecordSet{
			ObjectMeta: metav1.ObjectMeta{
				Name: r.privateDnsRecordName(),
			},
		}
		if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, privateRecord)); err != nil {
			return err
		}
		privateRecordInPublicZone := &awsdnsv1alpha1.ResourceRecordSet{
			ObjectMeta: metav1.ObjectMeta{
				Name: r.privateDnsRecordName() + legacyPrivateRecordSuffix,
			},
		}
		if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, privateRecordInPublicZone)); err != nil {
			return err
		}
	case consts.ProviderTypeGCP:
		if isUseLegacyDNS(r.Target) {
			// cleanup private if exists
			privateRecord := &gcpdnsv1alpha1.ResourceRecordSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: r.privateDnsRecordName(),
				},
			}
			if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, privateRecord)); err != nil {
				return err
			}
			privateRecordInPublicZone := &gcpdnsv1alpha1.ResourceRecordSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: r.privateDnsRecordName() + legacyPrivateRecordSuffix,
				},
			}
			if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, privateRecordInPublicZone)); err != nil {
				return err
			}
		} else {
			privateRecordInPublicZone := &awsdnsv1alpha1.ResourceRecordSet{
				ObjectMeta: metav1.ObjectMeta{
					Name: r.privateDnsRecordName() + legacyPrivateRecordSuffix,
				},
			}
			if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, privateRecordInPublicZone)); err != nil {
				return err
			}

		}
	default:
		return fmt.Errorf("unsupported provider %s", r.ProviderType)
	}
	return nil
}

func (r *reconcileContext) deleteEncryptionKey() error {
	key := &awskmsv1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name: r.Target.ClusterScopeName(),
		},
	}
	if err := client.IgnoreNotFound(r.Client.Delete(r.ctx, key)); err != nil {
		return errors.Annotate(err, "delete kms key")
	}
	return nil
}

func (r *reconcileContext) deleteNodeGroups() error {
	emptyNgSet := &nodegroup.Set{
		Name:       r.Target.PrefixedId(),
		NodeGroups: nil,
	}
	changes, err := r.Gardener.ApplyNodeGroupSet(r.ctx, r.networkGardenStatus.ShootName, emptyNgSet, gardener.NodeGroupPolicyScaleOnly)
	if err != nil {
		return errors.AddStack(err)
	}
	for _, change := range changes {
		r.Log.V(4).Info("cleanup node groups", "change", change.String())
	}
	return nil
}

func (r *reconcileContext) deleteTidbCluster() error {
	toDelete := &v1alpha1.TidbCluster{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: r.Target.PrefixedId(),
			Name:      consts.ClusterPrefix,
		},
	}
	err := r.shootClient.Delete(r.ctx, toDelete)
	return client.IgnoreNotFound(err)
}

func (r *reconcileContext) reclaimPVs() error {
	tc := &v1alpha1.TidbCluster{}
	err := r.shootClient.Get(r.ctx, types.NamespacedName{Namespace: r.Target.PrefixedId(), Name: consts.ClusterPrefix}, tc)
	if err == nil || !apierrors.IsNotFound(err) {
		r.Log.V(4).Info("waiting tc to be deleted, queueing...")
		return &reconcileerr.RequeueAfterError{RequeueAfter: defaultWaitInterval}
	}
	// do after tc deleted to avoid race-condition with tidb-operator
	// 1. delete PVCs
	err = r.shootClient.DeleteAllOf(r.ctx, &corev1.PersistentVolumeClaim{},
		client.InNamespace(r.Target.PrefixedId()),
		client.MatchingLabels(map[string]string{
			label.InstanceLabelKey: consts.ClusterPrefix,
		}))
	if err != nil {
		return errors.Annotate(err, "delete cluster PVCs")
	}
	// 2. patch PV reclaim policy
	pvList := &corev1.PersistentVolumeList{}
	// There are some pv which is not labeled. So we use .spec.claimRef to select the PV rather than label selector.
	err = r.shootClient.List(r.ctx, pvList)
	if err != nil {
		return errors.Annotate(err, "list cluster PVs")
	}
	var patchPVErros error
	for i := range pvList.Items {
		pv := pvList.Items[i]
		if pv.Spec.ClaimRef == nil || pv.Spec.ClaimRef.Namespace != r.Target.PrefixedId() {
			continue
		}
		if pv.Spec.PersistentVolumeReclaimPolicy != corev1.PersistentVolumeReclaimDelete {
			err := r.shootClient.Patch(r.ctx, &pv, client.RawPatch(types.MergePatchType, []byte(`{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}`)))
			if err != nil {
				patchPVErros = multierr.Append(patchPVErros, err)
			}
		}
	}
	return patchPVErros
}
