package br

import (
	"testing"

	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	operatorv1alpha1 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	clustergatewaymocks "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	dataplanemocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplane/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	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"
)

func TestStrictFlushWALRejectsPendingOperatorSuspension(t *testing.T) {
	sharedPool := &entity.SharedPool{
		SharedPool:       apiv1.SharedPool{Id: "100", DataplaneName: "dp-1"},
		PauseResumeState: entity.PauseResumeStatePausing,
	}
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	sharedPoolRepo.EXPECT().Load(mock.Anything, "100", false).Return(sharedPool, nil).Once()
	manager := dataplanemocks.NewManager(t)
	manager.EXPECT().GetClient("dp-1").Return(newPendingSuspensionClient(t), nil).Once()
	action := NewStrictFlushWALAction(sharedPoolRepo, nil, nil, manager, true)

	err := action.Invoke(actions.ActionArgs{workflow.SharedPoolIdKey: actions.ActionArgString("100")})
	require.ErrorContains(t, err, "operator suspend spec is true")
	require.True(t, actions.IsTerminalError(err))
}

func TestStrictFlushWALRunsWhileOperatorSuspensionIsPending(t *testing.T) {
	sharedPool := &entity.SharedPool{
		SharedPool:            apiv1.SharedPool{Id: "100", DataplaneName: "dp-1"},
		PauseResumeState:      entity.PauseResumeStatePausing,
		SuspendComputeDesired: true,
	}
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	sharedPoolRepo.EXPECT().Load(mock.Anything, "100", false).Return(sharedPool, nil).Twice()
	dataplaneRepo := repomocks.NewDataplaneRepo(t)
	dataplaneRepo.EXPECT().LoadByName(mock.Anything, "dp-1").Return(&entity.DataPlaneProvider{Name: "dp-1", GatewayUrl: "gateway.test"}, nil).Once()
	manager := dataplanemocks.NewManager(t)
	manager.EXPECT().GetClient("dp-1").Return(newPendingSuspensionClient(t), nil).Once()
	gateway := clustergatewaymocks.NewClient(t)
	gateway.EXPECT().FlushWAL(mock.Anything, sharedPool, tioperator.DefaultTiBRName).Return(nil).Once()
	action := NewStrictFlushWALAction(sharedPoolRepo, dataplaneRepo, gateway, manager, false)

	err := action.Invoke(actions.ActionArgs{workflow.SharedPoolIdKey: actions.ActionArgString("100")})
	require.NoError(t, err)
}

func newPendingSuspensionClient(t *testing.T) client.Client {
	t.Helper()
	scheme := runtime.NewScheme()
	require.NoError(t, v1alpha1br.AddToScheme(scheme))
	require.NoError(t, operatorv1alpha1.AddToScheme(scheme))
	namespace := entity.GetDataPlaneNamespace("100")
	cluster := &operatorv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{Name: "db", Namespace: namespace, Generation: 2},
		Spec:       operatorv1alpha1.ClusterSpec{SuspendAction: &operatorv1alpha1.SuspendAction{SuspendCompute: true}},
		Status:     operatorv1alpha1.ClusterStatus{ObservedGeneration: 1},
	}
	tibr := &v1alpha1br.TiBR{
		ObjectMeta: metav1.ObjectMeta{Name: tioperator.DefaultTiBRName, Namespace: namespace},
		Status: v1alpha1br.TiBRStatus{CommonStatus: operatorv1alpha1.CommonStatus{Conditions: []metav1.Condition{
			{Type: "Ready", Status: metav1.ConditionTrue},
			{Type: "Synced", Status: metav1.ConditionTrue},
		}}},
	}
	return fake.NewClientBuilder().WithScheme(scheme).WithObjects(cluster, tibr).Build()
}
