package compactbackup

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/log/zap"

	"github.com/tidbcloud/eks-provider/controllers/compactbackup/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

func testScheme(t *testing.T) *runtime.Scheme {
	t.Helper()
	s := runtime.NewScheme()
	require.NoError(t, cloudv1.AddToScheme(s))
	return s
}

func TestClusterFinalizer(t *testing.T) {
	assert.Equal(t, "compactbackup.cloud.pingcap.com/cb-1", task.ClusterFinalizer("cb-1"))
}

func TestHandleClusterGone(t *testing.T) {
	now := metav1.NewTime(time.Now())

	tests := []struct {
		name          string
		compactBackup *cloudv1.CompactBackup
		wantResult    ctrl.Result
		wantErr       bool
		wantPhase     cloudv1.CompactPhase
		wantMessage   string
	}{
		{
			name: "compactbackup_being_deleted_cluster_gone_removes_finalizer",
			compactBackup: &cloudv1.CompactBackup{
				ObjectMeta: metav1.ObjectMeta{
					Name:              "cb-1",
					Namespace:         "ns-1",
					DeletionTimestamp: &now,
					Finalizers:        []string{task.Finalizer},
				},
				Spec: cloudv1.CompactBackupSpec{
					ClusterName: "my-cluster",
				},
				Status: cloudv1.CompactBackupStatus{
					Phase: cloudv1.CompactPhaseRunning,
				},
			},
			wantResult: ctrl.Result{},
			wantErr:    false,
		},
		{
			name: "running_compactbackup_cluster_gone_marks_failed",
			compactBackup: &cloudv1.CompactBackup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "cb-2",
					Namespace: "ns-1",
				},
				Spec: cloudv1.CompactBackupSpec{
					ClusterName: "my-cluster",
				},
				Status: cloudv1.CompactBackupStatus{
					Phase: cloudv1.CompactPhaseRunning,
				},
			},
			wantResult:  ctrl.Result{},
			wantErr:     false,
			wantPhase:   cloudv1.CompactPhaseFailed,
			wantMessage: "cluster my-cluster has been deleted or is being deleted",
		},
		{
			name: "already_failed_compactbackup_cluster_gone_is_noop",
			compactBackup: &cloudv1.CompactBackup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "cb-3",
					Namespace: "ns-1",
				},
				Spec: cloudv1.CompactBackupSpec{
					ClusterName: "my-cluster",
				},
				Status: cloudv1.CompactBackupStatus{
					Phase:   cloudv1.CompactPhaseFailed,
					Message: "original failure",
				},
			},
			wantResult:  ctrl.Result{},
			wantErr:     false,
			wantPhase:   cloudv1.CompactPhaseFailed,
			wantMessage: "original failure", // unchanged
		},
		{
			name: "complete_compactbackup_cluster_gone_is_noop",
			compactBackup: &cloudv1.CompactBackup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "cb-4",
					Namespace: "ns-1",
				},
				Spec: cloudv1.CompactBackupSpec{
					ClusterName: "my-cluster",
				},
				Status: cloudv1.CompactBackupStatus{
					Phase: cloudv1.CompactPhaseComplete,
				},
			},
			wantResult: ctrl.Result{},
			wantErr:    false,
			wantPhase:  cloudv1.CompactPhaseComplete,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := testScheme(t)
			c := fake.NewClientBuilder().
				WithScheme(s).
				WithObjects(tt.compactBackup).
				WithStatusSubresource(tt.compactBackup).
				Build()

			r := &Reconciler{Client: c}
			logger := zap.New()

			result, err := r.handleClusterGone(context.Background(), tt.compactBackup, logger)

			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tt.wantResult, result)

			// Re-fetch the object to check persisted state
			got := &cloudv1.CompactBackup{}
			getErr := c.Get(context.Background(), client.ObjectKeyFromObject(tt.compactBackup), got)

			if tt.compactBackup.DeletionTimestamp != nil {
				// Fake client removes the object once the last finalizer is gone and DeletionTimestamp is set.
				assert.True(t, client.IgnoreNotFound(getErr) == nil, "expected object to be gone, got: %v", getErr)
			} else {
				require.NoError(t, getErr)
				assert.Equal(t, tt.wantPhase, got.Status.Phase)
				if tt.wantMessage != "" {
					assert.Equal(t, tt.wantMessage, got.Status.Message)
				}
			}
		})
	}
}
