package route53_test

import (
	"context"
	"testing"

	route53v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/route53/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	route53pause "github.com/tidbcloud/infra-provider/pkg/crossplane/route53"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

func TestReconcilePause(t *testing.T) {
	tests := []struct {
		name           string
		record         *route53v1alpha1.ResourceRecordSet
		wantRequeue    bool
		wantPaused     bool
		recordNotFound bool
	}{
		{
			name:           "missing record requeues",
			record:         newRecord("missing.example.com"),
			wantRequeue:    true,
			recordNotFound: true,
		},
		{
			name:        "unstable record requeues",
			record:      newRecord("unstable.example.com"),
			wantRequeue: true,
		},
		{
			name:       "stable record is paused",
			record:     newStableRecord("stable.example.com"),
			wantPaused: true,
		},
		{
			name:       "paused record remains paused",
			record:     newPausedRecord("paused.example.com"),
			wantPaused: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var objects []client.Object
			if !tt.recordNotFound {
				objects = append(objects, tt.record)
			}
			cli := newClient(t, objects...)

			needsRequeue, err := route53pause.ReconcilePause(context.Background(), cli, tt.record.GetName())
			require.NoError(t, err)
			require.Equal(t, tt.wantRequeue, needsRequeue)
			if tt.recordNotFound {
				return
			}

			current := getRecord(t, cli, tt.record.GetName())
			require.Equal(t, tt.wantPaused, meta.IsPaused(current))
		})
	}
}

func TestDeleteHookUnpausesBeforeDeletingRecord(t *testing.T) {
	rrs := newRecord("sql.example.com")
	rrs.Finalizers = []string{"test.finalizer"}
	rrs.Status.SetConditions(xpv1.Available(), xpv1.ReconcileSuccess())
	cli := newClient(t, rrs)

	_, err := route53pause.ReconcilePause(context.Background(), cli, rrs.GetName())
	require.NoError(t, err)
	require.True(t, meta.IsPaused(getRecord(t, cli, rrs.GetName())))

	err = k8s.DeleteObjectCompletedWithHooks(
		context.Background(),
		cli,
		&route53v1alpha1.ResourceRecordSet{ObjectMeta: metav1.ObjectMeta{Name: rrs.GetName()}},
		k8s.HookAutoUnpauseCrossplaneCR,
	)
	require.Error(t, err)

	deleting := getRecord(t, cli, rrs.GetName())
	require.False(t, deleting.GetDeletionTimestamp().IsZero())
	require.Empty(t, deleting.Annotations[meta.AnnotationKeyReconciliationPaused])
}

func newRecord(name string) *route53v1alpha1.ResourceRecordSet {
	return &route53v1alpha1.ResourceRecordSet{ObjectMeta: metav1.ObjectMeta{Name: name}}
}

func newStableRecord(name string) *route53v1alpha1.ResourceRecordSet {
	rrs := newRecord(name)
	rrs.Status.SetConditions(xpv1.Available(), xpv1.ReconcileSuccess())
	return rrs
}

func newPausedRecord(name string) *route53v1alpha1.ResourceRecordSet {
	rrs := newRecord(name)
	rrs.Annotations = map[string]string{meta.AnnotationKeyReconciliationPaused: "true"}
	rrs.Status.SetConditions(xpv1.Available(), xpv1.ReconcilePaused())
	return rrs
}

func newClient(t *testing.T, objects ...client.Object) client.Client {
	t.Helper()
	scheme := runtime.NewScheme()
	require.NoError(t, route53v1alpha1.SchemeBuilder.AddToScheme(scheme))
	return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
}

func getRecord(t *testing.T, cli client.Client, name string) *route53v1alpha1.ResourceRecordSet {
	t.Helper()
	rrs := &route53v1alpha1.ResourceRecordSet{}
	require.NoError(t, cli.Get(context.Background(), client.ObjectKey{Name: name}, rrs))
	return rrs
}
