package service

import (
	"context"
	"testing"

	opv2core "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"

	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/infra-provider/cluster-agent/mocks/kubeclient"
)

func TestOperatorV2Service_DeleteGroupInstances(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockClient := kubeclient.NewMockClient(ctrl)
	service := &operatorV2Service{
		dataplaneCli: mockClient,
	}

	mockScheme := createOperatorV2Scheme()

	tests := []struct {
		name        string
		req         *apiv1.DeleteGroupInstancesReq
		setupMocks  func()
		wantErr     bool
		wantErrCode codes.Code
		wantErrMsg  string
	}{
		{
			name: "successful deletion of ticdc group instances",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
				GroupName:     "ticdc-group",
			},
			setupMocks: func() {
				// Mock Scheme().New for TiCDCGroup
				mockClient.EXPECT().Scheme().Return(mockScheme).Times(1)

				// Mock Get for TiCDCGroup
				groupObj := &opv2core.TiCDCGroup{}
				groupObj.Name = "ticdc-group"
				groupObj.Namespace = "test-namespace"

				mockClient.EXPECT().Get(
					gomock.Any(),
					client.ObjectKey{Namespace: "test-namespace", Name: "ticdc-group"},
					gomock.Any(),
				).SetArg(2, *groupObj).Return(nil).Times(1)

				// Create mock instances
				subItem1 := &opv2core.TiCDC{}
				subItem1.Name = "ticdc-group-abc"
				subItem1.Namespace = "test-namespace"
				subItem1.Labels = map[string]string{
					PingcapComponentLabelKey: "ticdc",
					PingcapGroupLabelKey:     "ticdc-group",
				}

				subItem2 := &opv2core.TiCDC{}
				subItem2.Name = "ticdc-group-def"
				subItem2.Namespace = "test-namespace"
				subItem2.Labels = map[string]string{
					PingcapComponentLabelKey: "ticdc",
					PingcapGroupLabelKey:     "ticdc-group",
				}

				// mock instance list
				instanceList := &opv2core.TiCDCList{}
				instanceList.Items = []opv2core.TiCDC{*subItem1, *subItem2}

				mockClient.EXPECT().List(
					gomock.Any(),
					gomock.Any(),
					client.InNamespace("test-namespace"),
					client.MatchingLabels{
						PingcapComponentLabelKey: "ticdc",
						PingcapGroupLabelKey:     "ticdc-group",
					},
				).SetArg(1, *instanceList).Return(nil).Times(1)

				// Mock Delete for each instance
				mockClient.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(nil).Times(2)
			},
			wantErr: false,
		},
		{
			name: "missing namespace",
			req: &apiv1.DeleteGroupInstancesReq{
				ComponentName: "ticdc",
				GroupName:     "ticdc-group",
			},
			setupMocks:  func() {},
			wantErr:     true,
			wantErrCode: codes.InvalidArgument,
			wantErrMsg:  "namespace is required",
		},
		{
			name: "missing component name",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace: "test-namespace",
				GroupName: "ticdc-group",
			},
			setupMocks:  func() {},
			wantErr:     true,
			wantErrCode: codes.InvalidArgument,
			wantErrMsg:  "component name is required",
		},
		{
			name: "missing group name",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
			},
			setupMocks:  func() {},
			wantErr:     true,
			wantErrCode: codes.InvalidArgument,
			wantErrMsg:  "group name is required",
		},
		{
			name: "invalid component name",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "invalid-component",
				GroupName:     "ticdc-group",
			},
			setupMocks:  func() {},
			wantErr:     true,
			wantErrCode: codes.InvalidArgument,
			wantErrMsg:  "invalid component name: invalid-component, should be one of",
		},
		{
			name: "not allowed component name",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "tikv",
				GroupName:     "tikv-group",
			},
			setupMocks:  func() {},
			wantErr:     true,
			wantErrCode: codes.InvalidArgument,
			wantErrMsg:  "invalid component name: tikv, should be one of [ticdc]",
		},
		{
			name: "group not found",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
				GroupName:     "non-existent-group",
			},
			setupMocks: func() {
				// Mock Scheme().New for TiCDCGroup
				mockClient.EXPECT().Scheme().Return(mockScheme).Times(1)

				// Mock Get for TiCDCGroup - return not found error
				mockClient.EXPECT().Get(
					gomock.Any(),
					client.ObjectKey{Namespace: "test-namespace", Name: "non-existent-group"},
					gomock.Any(),
				).Return(apierrors.NewNotFound(schema.GroupResource{}, "non-existent-group")).Times(1)
			},
			wantErr:     true,
			wantErrCode: codes.NotFound,
			wantErrMsg:  "TiCDCGroup(non-existent-group) not found in namespace test-namespace",
		},
		{
			name: "failed to get group - internal error",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
				GroupName:     "ticdc-group",
			},
			setupMocks: func() {
				// Mock Scheme().New for TiCDCGroup
				mockClient.EXPECT().Scheme().Return(mockScheme).Times(1)

				// Mock Get for TiCDCGroup - return internal error
				mockClient.EXPECT().Get(
					gomock.Any(),
					client.ObjectKey{Namespace: "test-namespace", Name: "ticdc-group"},
					gomock.Any(),
				).Return(apierrors.NewInternalError(assert.AnError)).Times(1)
			},
			wantErr:     true,
			wantErrCode: codes.Internal,
			wantErrMsg:  "failed to get TiCDCGroup(ticdc-group) in namespace test-namespace: Internal error occurred",
		},
		{
			name: "failed to list instances",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
				GroupName:     "ticdc-group",
			},
			setupMocks: func() {
				// Mock Scheme().New for TiCDCGroup
				mockClient.EXPECT().Scheme().Return(mockScheme).Times(1)

				// Mock Get for TiCDCGroup
				groupObj := &opv2core.TiCDCGroup{}
				groupObj.Name = "ticdc-group"
				groupObj.Namespace = "test-namespace"

				mockClient.EXPECT().Get(
					gomock.Any(),
					client.ObjectKey{Namespace: "test-namespace", Name: "ticdc-group"},
					gomock.Any(),
				).SetArg(2, *groupObj).Return(nil).Times(1)

				// Mock List for instances - return error
				mockClient.EXPECT().List(
					gomock.Any(),
					gomock.Any(),
					client.InNamespace("test-namespace"),
					client.MatchingLabels{
						PingcapComponentLabelKey: "ticdc",
						PingcapGroupLabelKey:     "ticdc-group",
					},
				).Return(apierrors.NewInternalError(assert.AnError)).Times(1)
			},
			wantErr:     true,
			wantErrCode: codes.Internal,
			wantErrMsg:  "failed to list instances: Internal error occurred",
		},
		{
			name: "failed to delete instance",
			req: &apiv1.DeleteGroupInstancesReq{
				Namespace:     "test-namespace",
				ComponentName: "ticdc",
				GroupName:     "ticdc-group",
			},
			setupMocks: func() {
				// Mock Scheme().New for TiCDCGroup
				mockClient.EXPECT().Scheme().Return(mockScheme).Times(1)

				// Mock Get for TiCDCGroup
				groupObj := &opv2core.TiCDCGroup{}
				groupObj.Name = "ticdc-group"
				groupObj.Namespace = "test-namespace"

				mockClient.EXPECT().Get(
					gomock.Any(),
					client.ObjectKey{Namespace: "test-namespace", Name: "ticdc-group"},
					gomock.Any(),
				).SetArg(2, *groupObj).Return(nil).Times(1)

				// Create mock instances
				subItem1 := &opv2core.TiCDC{}
				subItem1.Name = "ticdc-group-abc"
				subItem1.Namespace = "test-namespace"
				subItem1.Labels = map[string]string{
					PingcapComponentLabelKey: "ticdc",
					PingcapGroupLabelKey:     "ticdc-group",
				}
				// Mock List for instances
				instanceList := &opv2core.TiCDCList{}
				instanceList.Items = []opv2core.TiCDC{*subItem1}

				mockClient.EXPECT().List(
					gomock.Any(),
					gomock.Any(),
					client.InNamespace("test-namespace"),
					client.MatchingLabels{
						PingcapComponentLabelKey: "ticdc",
						PingcapGroupLabelKey:     "ticdc-group",
					},
				).SetArg(1, *instanceList).Return(nil).Times(1)

				// Mock Delete for instance - return error
				mockClient.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(apierrors.NewInternalError(assert.AnError)).Times(1)
			},
			wantErr:     true,
			wantErrCode: codes.Internal,
			wantErrMsg:  "failed to delete instance: Internal error occurred",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Setup mocks
			tt.setupMocks()

			// Execute function
			ctx := context.Background()
			result, err := service.DeleteGroupInstances(ctx, tt.req)

			// Assert results
			if tt.wantErr {
				require.Error(t, err)
				st, ok := status.FromError(err)
				require.True(t, ok, "error should be a gRPC status error")
				assert.Equal(t, tt.wantErrCode, st.Code())
				assert.Contains(t, st.Message(), tt.wantErrMsg)
				assert.Nil(t, result)
			} else {
				require.NoError(t, err)
				assert.Equal(t, &emptypb.Empty{}, result)
			}
		})
	}
}

// Test helper function to create a mock scheme that can create objects
func createOperatorV2Scheme() *runtime.Scheme {
	scheme := runtime.NewScheme()

	utilruntime.Must(opv2core.AddToScheme(scheme))

	return scheme
}
