package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"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"
	clusterconfigmocks "github.com/tidbcloud/cluster-service-ng/pkg/clusterconfig/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestValidateCreateSharedPoolTiflashMode(t *testing.T) {
	tests := []struct {
		name          string
		capacityMode  apiv1.CapacityMode
		deployType    string
		requestedMode apiv1.TiflashMode
		components    []*entity.Component
		wantCode      codes.Code
	}{
		{
			name:          "auto allows RCU driven Essential v2 pool",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			deployType:    schema.SharedPoolDeployTypeSharedTiProxy,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_AUTO,
		},
		{
			name:          "columnar allows RCU driven Essential v2 pool",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			deployType:    schema.SharedPoolDeployTypeSharedTiProxy,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
		},
		{
			name:          "auto does not validate RCU driven schema manager topology",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_AUTO,
			components: []*entity.Component{
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
		},
		{
			name:          "legacy allows RCU driven non shared TiProxy pool",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_LEGACY,
		},
		{
			name:          "columnar allows topology driven pool",
			capacityMode:  apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			components: []*entity.Component{
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
		},
		{
			name:          "columnar requires topology driven schema manager",
			capacityMode:  apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			wantCode:      codes.InvalidArgument,
		},
		{
			name:          "columnar rejects duplicate topology driven schema manager",
			capacityMode:  apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			components: []*entity.Component{
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
			wantCode: codes.InvalidArgument,
		},
		{
			name:          "columnar rejects topology driven tiflash write",
			capacityMode:  apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			components: []*entity.Component{
				{ComponentType: apiv1.ComponentType_TIFLASH_WRITE},
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
			wantCode: codes.InvalidArgument,
		},
		{
			name:          "auto rejects topology driven schema manager",
			capacityMode:  apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_AUTO,
			components: []*entity.Component{
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
			wantCode: codes.InvalidArgument,
		},
		{
			name:          "legacy rejects Essential v2 pool",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			deployType:    schema.SharedPoolDeployTypeSharedTiProxy,
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_LEGACY,
			wantCode:      codes.InvalidArgument,
		},
		{
			name:          "unknown requested mode is rejected",
			capacityMode:  apiv1.CapacityMode_RCU_DRIVEN,
			requestedMode: apiv1.TiflashMode(99),
			wantCode:      codes.InvalidArgument,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateCreateSharedPoolTiflashMode(&entity.SharedPool{
				SharedPool: apiv1.SharedPool{
					CapacityMode: tt.capacityMode,
					TopologySpec: &apiv1.TopologySpec{Components: tt.components},
				},
				DeployType:           tt.deployType,
				RequestedTiflashMode: tt.requestedMode,
			})
			if tt.wantCode == codes.OK {
				require.NoError(t, err)
				return
			}
			require.Error(t, err)
			assert.Equal(t, tt.wantCode, status.Code(err))
		})
	}
}

func TestResolveCreateSharedPoolTiflashMode(t *testing.T) {
	tests := []struct {
		name          string
		requestedMode apiv1.TiflashMode
		deployType    string
		imageTag      string
		lookupErr     error
		lookup        bool
		wantMarker    string
		wantCode      codes.Code
	}{
		{
			name:       "unspecified uses columnar when TiKV image is compatible",
			imageTag:   "v26.3.5-nextgen",
			lookup:     true,
			wantMarker: schema.SharedPoolTiflashModeColumnar,
		},
		{
			name:     "auto falls back to legacy for old TiKV image",
			imageTag: "v26.3.4-nextgen",
			lookup:   true,
		},
		{
			name:     "auto falls back to legacy for malformed TiKV image",
			imageTag: "not-a-version",
			lookup:   true,
		},
		{
			name:          "legacy skips effective TiKV image lookup",
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_LEGACY,
		},
		{
			name:       "Essential v2 always uses columnar without effective TiKV image lookup",
			deployType: schema.SharedPoolDeployTypeSharedTiProxy,
			wantMarker: schema.SharedPoolTiflashModeColumnar,
		},
		{
			name:          "columnar marks pool without effective TiKV image lookup",
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			wantMarker:    schema.SharedPoolTiflashModeColumnar,
		},
		{
			name:          "columnar accepts malformed TiKV image by skipping lookup",
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			wantMarker:    schema.SharedPoolTiflashModeColumnar,
		},
		{
			name:          "columnar skips unresolved TiKV image lookup",
			requestedMode: apiv1.TiflashMode_TIFLASH_MODE_COLUMNAR,
			wantMarker:    schema.SharedPoolTiflashModeColumnar,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			clusterConfigClient := clusterconfigmocks.NewClient(t)
			sharedPool := &entity.SharedPool{
				SharedPool:           apiv1.SharedPool{Id: "sharedpool1"},
				DeployType:           tt.deployType,
				RequestedTiflashMode: tt.requestedMode,
			}
			if tt.lookup {
				clusterConfigClient.On("GetEffectiveComponentImageTag", mock.Anything, sharedPool, apiv1.ComponentType_TIKV).
					Return(tt.imageTag, tt.lookupErr).
					Once()
			}

			err := (&serverImpl{clusterConfigClient: clusterConfigClient}).resolveCreateSharedPoolTiflashMode(context.Background(), sharedPool)
			if tt.wantCode == codes.OK {
				require.NoError(t, err)
				assert.Equal(t, tt.wantMarker, sharedPool.TiflashMode)
			} else {
				require.Error(t, err)
				assert.Equal(t, tt.wantCode, status.Code(err))
			}
			if !tt.lookup {
				clusterConfigClient.AssertNotCalled(t, "GetEffectiveComponentImageTag", mock.Anything, mock.Anything, mock.Anything)
			}
		})
	}
}
