package serverless

import (
	"context"
	"testing"
	"time"

	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestReconcile_CreateAddsFinalizerAndRequeues(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-time.Minute)),
		},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	// The reconciler now completes the full cycle and sets phase to ACTIVE
	assert.False(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	// Phase should be set to ACTIVE after successful reconciliation
	assert.Equal(t, constants.ACTIVE, updated.Status.Phase)
	// Annotations should be set for task timing
	assert.Contains(t, updated.Annotations, "first-pass-when-creating.cloud.pingcap.com/Guardian")
	assert.Contains(t, updated.Annotations, "first-pass-when-creating.cloud.pingcap.com/All")
}

func TestReconcile_CreatePromotesCluster(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Minute)),
		},
		Status: observabilitypingcapcomv1.ClusterStatus{Phase: constants.CREATING},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	assert.False(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	assert.Equal(t, constants.ACTIVE, updated.Status.Phase)
	assert.Contains(t, updated.Annotations, "first-pass-when-creating.cloud.pingcap.com/Guardian")
	assert.Contains(t, updated.Annotations, "first-pass-when-creating.cloud.pingcap.com/All")
}

func TestReconcile_DeleteRequeues(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	now := metav1.NewTime(time.Now())
	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-3 * time.Minute)),
			DeletionTimestamp: &now,
			Finalizers:        []string{""},
		},
		Status: observabilitypingcapcomv1.ClusterStatus{Phase: constants.ACTIVE},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	assert.True(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	assert.Equal(t, constants.DELETING, updated.Status.Phase)
}

func TestReconcile_ClusterNotFound(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cli := fake.NewClientBuilder().WithScheme(scheme).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: "non-existent", Namespace: "test-namespace"}})
	require.NoError(t, err)
	assert.False(t, res.Requeue)
	assert.Equal(t, time.Duration(0), res.RequeueAfter)
}

func TestReconcile_UpdatePhaseFromCreatingToActive(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Minute)),
		},
		Status: observabilitypingcapcomv1.ClusterStatus{Phase: constants.CREATING},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	assert.False(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	assert.Equal(t, constants.ACTIVE, updated.Status.Phase)
}

func TestReconcile_AnnotationNotSetTwice(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-2 * time.Minute)),
			Annotations: map[string]string{
				"first-pass-when-creating.cloud.pingcap.com/Guardian": "1m0s",
			},
		},
		Status: observabilitypingcapcomv1.ClusterStatus{Phase: constants.CREATING},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	assert.False(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	// Guardian annotation should still be the original value
	assert.Equal(t, "1m0s", updated.Annotations["first-pass-when-creating.cloud.pingcap.com/Guardian"])
}

func TestReconcile_ActiveClusterNoChange(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, observabilitypingcapcomv1.AddToScheme(scheme))

	cluster := &observabilitypingcapcomv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:              "test-cluster",
			Namespace:         "test-namespace",
			CreationTimestamp: metav1.NewTime(time.Now().Add(-5 * time.Minute)),
			Annotations: map[string]string{
				"first-pass-when-creating.cloud.pingcap.com/Guardian": "1m0s",
				"first-pass-when-creating.cloud.pingcap.com/All":      "2m0s",
			},
		},
		Status: observabilitypingcapcomv1.ClusterStatus{Phase: constants.ACTIVE},
	}

	cli := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(cluster).WithObjects(cluster.DeepCopy()).Build()

	reconciler := &ServerlessClusterReconciler{
		Client:    cli,
		RdsClient: cli,
		Scheme:    scheme,
		Logger:    testr.New(t),
	}

	res, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}})
	require.NoError(t, err)
	assert.False(t, res.Requeue)

	updated := &observabilitypingcapcomv1.Cluster{}
	require.NoError(t, cli.Get(context.Background(), types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, updated))
	assert.Equal(t, constants.ACTIVE, updated.Status.Phase)
	assert.Equal(t, "1m0s", updated.Annotations["first-pass-when-creating.cloud.pingcap.com/Guardian"])
	assert.Equal(t, "2m0s", updated.Annotations["first-pass-when-creating.cloud.pingcap.com/All"])
}
