package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	clustergatewaymocks "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	dataplanemocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplanemgr/mocks"
	executormocks "github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor/mocks"
	repomnocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestCreatePrivateLinkService(t *testing.T) {
	tests := []struct {
		name               string
		privateLinkService *entity.PrivateLinkService
		setupMocks         func(*repomnocks.TiDBGroupRepo, *dataplanemocks.Client, *executormocks.RemoteExecutor)
		expectedError      string
		expectedCode       codes.Code
		validate           func(*testing.T, *entity.PrivateLinkService, error)
	}{
		{
			name: "successful create private link service",
			privateLinkService: &entity.PrivateLinkService{
				Id:          "test-pls",
				TidbGroupId: "testgroup",
				Connections: []*entity.PrivateLinkConnection{
					{
						Info: &apiv1.PrivateLinkService_Connection_Aws_{
							Aws: &apiv1.PrivateLinkService_Connection_Aws{
								VpcEndpointId: "vpce-123",
							},
						},
					},
				},
			},
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, dataplaneClient *dataplanemocks.Client, dagExecutor *executormocks.RemoteExecutor) {
				// Mock tidb group load
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:            "testgroup",
						ClusterId:     "testclusterid", // Add the cluster ID
						Phase:         apiv1.TiDBGroup_AVAILABLE,
						DataplaneName: "testdataplane",
						SqlEndpoint: &apiv1.TiDBGroup_SqlEndpoint{
							PrivateLb: &entity.LoadBalancer{
								Enabled: true,
							},
						},
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)

				// Mock dataplane client - returns *dplanemgrv1.PrivateLinkService
				mockPLS := &dplanemgrv1.PrivateLinkService{
					Id:             "test-pls",
					ServiceDnsName: "testgroup-private-svc.example.com",
				}
				dataplaneClient.On("CreatePrivateLinkService", mock.Anything, mock.MatchedBy(func(req *dplanemgrv1.CreatePrivateLinkServiceRequest) bool {
					connections := req.GetPrivateLinkService().GetConnections()
					return req.GetDataPlaneId() == "testdataplane" &&
						req.GetId() == "test-pls" &&
						len(connections) == 1 &&
						connections[0].GetAws().GetVpcEndpointId() == "vpce-123"
				})).Return(mockPLS, nil)
			},
			validate: func(t *testing.T, result *entity.PrivateLinkService, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				require.Equal(t, "test-pls", result.GetId())
				require.Equal(t, "testgroup", result.GetTidbGroupId())
			},
		},
		{
			name: "missing private link service id",
			privateLinkService: &entity.PrivateLinkService{
				TidbGroupId: "testgroup",
			},
			setupMocks:    func(*repomnocks.TiDBGroupRepo, *dataplanemocks.Client, *executormocks.RemoteExecutor) {},
			expectedError: "privateLinkServiceId are required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name: "missing tidb group id",
			privateLinkService: &entity.PrivateLinkService{
				Id: "test-pls",
			},
			setupMocks:    func(*repomnocks.TiDBGroupRepo, *dataplanemocks.Client, *executormocks.RemoteExecutor) {},
			expectedError: "tidbGroupId are required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name: "tidb group not found",
			privateLinkService: &entity.PrivateLinkService{
				Id:          "test-pls",
				TidbGroupId: "nonexistent",
			},
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, dataplaneClient *dataplanemocks.Client, dagExecutor *executormocks.RemoteExecutor) {
				tidbGroupRepo.On("Load", mock.Anything, "nonexistent", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "tidbGroup nonexistent not found",
			expectedCode:  codes.NotFound,
		},
		{
			name: "tidb group not ready",
			privateLinkService: &entity.PrivateLinkService{
				Id:          "test-pls",
				TidbGroupId: "testgroup",
			},
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, dataplaneClient *dataplanemocks.Client, dagExecutor *executormocks.RemoteExecutor) {
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:        "testgroup",
						ClusterId: "testclusterid", // Add the cluster ID
						Phase:     apiv1.TiDBGroup_CREATING,
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)
			},
			expectedError: "tidbGroup is not in a valid state for creating a private link service",
			expectedCode:  codes.FailedPrecondition,
		},
		{
			name: "private link service not enabled",
			privateLinkService: &entity.PrivateLinkService{
				Id:          "test-pls",
				TidbGroupId: "testgroup",
			},
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, dataplaneClient *dataplanemocks.Client, dagExecutor *executormocks.RemoteExecutor) {
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:        "testgroup",
						ClusterId: "testclusterid", // Add the cluster ID
						Phase:     apiv1.TiDBGroup_AVAILABLE,
						SqlEndpoint: &apiv1.TiDBGroup_SqlEndpoint{
							PrivateLb: &entity.LoadBalancer{
								Enabled: false,
							},
						},
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)
			},
			expectedError: "tidbGroup does not have private load balance enabled",
			expectedCode:  codes.FailedPrecondition,
		},
		{
			name: "dataplane client error",
			privateLinkService: &entity.PrivateLinkService{
				Id:          "test-pls",
				TidbGroupId: "testgroup",
			},
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, dataplaneClient *dataplanemocks.Client, dagExecutor *executormocks.RemoteExecutor) {
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:            "testgroup",
						ClusterId:     "testclusterid", // Add the cluster ID
						Phase:         apiv1.TiDBGroup_AVAILABLE,
						DataplaneName: "testdataplane",
						SqlEndpoint: &apiv1.TiDBGroup_SqlEndpoint{
							PrivateLb: &entity.LoadBalancer{
								Enabled: true,
							},
						},
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)

				dataplaneClient.On("CreatePrivateLinkService", mock.Anything, mock.Anything).Return(nil, status.Error(codes.Internal, "dataplane error"))
			},
			expectedError: "dataplane error", // The actual error would be wrapped by the client
			expectedCode:  codes.Internal,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			tidbGroupRepo := repomnocks.NewTiDBGroupRepo(t)
			dataplaneClient := dataplanemocks.NewClient(t)
			dagExecutor := executormocks.NewRemoteExecutor(t)
			clusterGatewayClient := clustergatewaymocks.NewClient(t)

			// Setup mocks
			tt.setupMocks(tidbGroupRepo, dataplaneClient, dagExecutor)

			// Create server implementation
			server := &serverImpl{
				tidbGroupRepo:     tidbGroupRepo,
				sharedPoolRepo:    availableSharedPoolRepoForMutationTest(),
				dataplaneClient:   dataplaneClient,
				dagExecutor:       dagExecutor,
				transactioner:     &mockTransactioner{},
				clustergatewayCli: clusterGatewayClient,
			}

			// Execute test
			result, err := server.CreatePrivateLinkService(context.Background(), "testclusterid", tt.privateLinkService)

			// Validate results
			switch {
			case tt.expectedError != "":
				require.Error(t, err)
				require.Contains(t, err.Error(), tt.expectedError)
				if tt.expectedCode != codes.OK {
					require.Equal(t, tt.expectedCode, status.Code(err))
				}
				require.Nil(t, result)
			case tt.validate != nil:
				tt.validate(t, result, err)
			default:
				require.NoError(t, err)
				require.NotNil(t, result)
			}
		})
	}
}
