package customstatus

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/pkgv2/db"
	"go.uber.org/mock/gomock"
	"gorm.io/gorm"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/nextgenglobal/mock_nextgenglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
)

type lockErrorCustomStatusRepository struct {
	repository.CustomStatusRepository
}

func (lockErrorCustomStatusRepository) LockClustersForUpdate(context.Context, []uint64) error {
	return gorm.ErrRecordNotFound
}

type passthroughCustomStatusTransProvider struct {
	db.TransProvider
}

func (passthroughCustomStatusTransProvider) Transaction(ctx context.Context, callback func(context.Context) error) error {
	return callback(ctx)
}

func TestSetClusterCustomStatusPreservesClusterNotFoundFromLock(t *testing.T) {
	t.Parallel()

	service := &service{
		customStatusRepo: lockErrorCustomStatusRepository{},
		transaction:      passthroughCustomStatusTransProvider{},
	}

	_, _, _, err := service.SetClusterCustomStatus(t.Context(), &entity.ClusterCustomStatus{ClusterId: 100})

	require.Error(t, err)
	require.ErrorIs(t, err, constant.ErrClusterNotFound)
}

func TestNextgenCustomStatusIsBlockedDuringResourcePoolPausePrecheck(t *testing.T) {
	ctrl := gomock.NewController(t)
	nextgenCli := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	nextgenCli.EXPECT().GetCluster(gomock.Any(), &globalngv1.GetClusterRequest{
		ClusterId: 1,
	}).Return(&globalngv1.Cluster{
		ClusterId:    1,
		SharedPoolId: "100",
		State:        globalngv1.Cluster_AVAILABLE,
	}, nil)
	nextgenCli.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 100,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			LifecycleState: globalngv1.ResourcePool_PAUSE_PRECHECKING,
		},
	}, nil)

	service := &service{nextgenCli: nextgenCli}
	mutex, state, err := service.isMutexClusterState(t.Context(), &entity.Cluster{
		ClusterId:  1,
		DeployType: entity.DeployTypeNextGen,
	}, &entity.ClusterCustomStatus{
		ClusterId:  1,
		StatusType: tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_CDC,
		CustomMutex: &tidbmgmtservicev1.ClusterCustomStatus_CustomMutex{
			ClusterAction: []tidbmgmtservicev1.ClusterAction{
				tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_PAUSE_CLUSTER,
			},
		},
	})

	require.NoError(t, err)
	require.True(t, mutex)
	require.Equal(t, openapicommonv1beta1.ClusterState_PAUSING, state)
}

func TestNextgenPauseMutexResourcePoolStates(t *testing.T) {
	tests := []struct {
		name      string
		poolState globalngv1.ResourcePool_LifecycleState
		mutex     bool
		state     openapicommonv1beta1.ClusterState
	}{
		{
			name:      "pause prechecking",
			poolState: globalngv1.ResourcePool_PAUSE_PRECHECKING,
			mutex:     true,
			state:     openapicommonv1beta1.ClusterState_PAUSING,
		},
		{
			name:      "pausing",
			poolState: globalngv1.ResourcePool_PAUSING,
			mutex:     true,
			state:     openapicommonv1beta1.ClusterState_PAUSING,
		},
		{
			name:      "paused",
			poolState: globalngv1.ResourcePool_PAUSED,
			mutex:     true,
			state:     openapicommonv1beta1.ClusterState_PAUSED,
		},
		{
			name:      "resuming",
			poolState: globalngv1.ResourcePool_RESUMING,
			mutex:     true,
			state:     openapicommonv1beta1.ClusterState_RESUMING,
		},
		{
			name:      "active",
			poolState: globalngv1.ResourcePool_ACTIVE,
			state:     openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mutex, state := ifNextgenResourcePoolStateCausedByAction(
				tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_PAUSE_CLUSTER,
				tt.poolState,
			)
			require.Equal(t, tt.mutex, mutex)
			require.Equal(t, tt.state, state)
		})
	}
}
