package action

import (
	"errors"
	"testing"

	"github.com/pingcap/kvproto/pkg/keyspacepb"
	"github.com/prometheus/client_golang/prometheus/testutil"
	"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"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
	cgmocks "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	pdapiv2 "github.com/tikv/pd/server/apiv2/handlers"
	"google.golang.org/protobuf/encoding/protojson"
)

func TestSetKeySpaceAction_RuVersion404(t *testing.T) {
	tests := []struct {
		name              string
		ruVersion         int32
		setRuVersionErr   error
		expectError       bool
		expectMetricDelta float64
	}{
		{
			name:              "ruVersion=2, PD returns 404 → metric incremented, no error",
			ruVersion:         2,
			setRuVersionErr:   clustergateway.ErrRuVersionNotSupported,
			expectError:       false,
			expectMetricDelta: 1,
		},
		{
			name:              "ruVersion=3, PD returns 404 → no metric, no error",
			ruVersion:         3,
			setRuVersionErr:   clustergateway.ErrRuVersionNotSupported,
			expectError:       false,
			expectMetricDelta: 0,
		},
		{
			name:              "ruVersion=2, success → no metric, no error",
			ruVersion:         2,
			setRuVersionErr:   nil,
			expectError:       false,
			expectMetricDelta: 0,
		},
		{
			name:              "ruVersion=2, other error → returns error",
			ruVersion:         2,
			setRuVersionErr:   errors.New("connection refused"),
			expectError:       true,
			expectMetricDelta: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			clusterGatewayCli := cgmocks.NewClient(t)
			sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
			tidbClusterRepo := repomocks.NewTiDBClusterRepo(t)

			tidbCluster := &apiv1.Cluster{
				Id:        "ks-test-456",
				RuVersion: tt.ruVersion,
				RequestUnit: &apiv1.Cluster_RequestUnit{
					Max: 1000,
				},
				SharedPoolId: "sp-2",
			}
			sharedPool := &entity.SharedPool{
				SharedPool: apiv1.SharedPool{Id: "sp-2"},
			}

			sharedPoolRepo.On("Load", mock.Anything, "sp-2", false).
				Return(sharedPool, nil)
			tidbClusterRepo.On("Load", mock.Anything, "ks-test-456", false).
				Return(tidbCluster, nil)

			tidbClusterStr, err := protojson.Marshal(tidbCluster)
			require.NoError(t, err)

			// GetKeySpace returns existing enabled keyspace
			clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "ks-test-456").
				Return(&pdapiv2.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ENABLED,
					},
				}, nil)
			clusterGatewayCli.On("SetKeySpaceResourceGroup", mock.Anything, sharedPool, "ks-test-456", mock.Anything).
				Return(nil)

			if tt.ruVersion > 0 {
				clusterGatewayCli.On("SetKeySpaceRuVersion", mock.Anything, sharedPool, "ks-test-456", mock.Anything).
					Return(tt.setRuVersionErr)
			}

			action := NewSetKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			args := actions.ActionArgs{
				workflow.SharedPoolIdKey:  actions.ActionArgString("sp-2"),
				workflow.TiDBClusterIdKey: actions.ActionArgString("ks-test-456"),
				workflow.CmekIdKey:        actions.ActionArgString(""),
				workflow.TiDBClusterKey:   actions.ActionArgString(string(tidbClusterStr)),
			}

			// Reset metric before test
			RuVersionUnsupportedTotal.Reset()

			err = action.Invoke(args)

			if tt.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			metricValue := testutil.ToFloat64(RuVersionUnsupportedTotal.WithLabelValues("sp-2", "ks-test-456"))
			require.InDelta(t, tt.expectMetricDelta, metricValue, 1e-9)
		})
	}
}

func TestSetKeySpaceAction_UsesTiDBClusterArgsForEncryptionContext(t *testing.T) {
	clusterGatewayCli := cgmocks.NewClient(t)
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	tidbClusterRepo := repomocks.NewTiDBClusterRepo(t)

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{Id: "sp-2"},
	}
	sharedPoolRepo.On("Load", mock.Anything, "sp-2", false).
		Return(sharedPool, nil)
	tidbClusterRepo.On("Load", mock.Anything, "ks-test-456", false).
		Return(&apiv1.Cluster{
			Id: "ks-test-456",
			RequestUnit: &apiv1.Cluster_RequestUnit{
				Max: 1000,
			},
			SharedPoolId: "sp-2",
			RuVersion:    2,
		}, nil)

	tidbCluster := &apiv1.Cluster{
		Id: "ks-test-456",
		RequestUnit: &apiv1.Cluster_RequestUnit{
			Max: 1000,
		},
		SharedPoolId: "sp-2",
		RuVersion:    2,
		SecurityConfiguration: &apiv1.Cluster_SecurityConfiguration{
			Cmek: &apiv1.Cluster_SecurityConfiguration_AwsMasterKeyArn{
				AwsMasterKeyArn: "arn:aws:kms:us-east-1:123456789012:key/test",
			},
			EncryptionContext: map[string]string{
				"ExternalId": "60205",
			},
		},
	}
	tidbClusterStr, err := protojson.Marshal(tidbCluster)
	require.NoError(t, err)

	clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "ks-test-456").
		Return(nil, clustergateway.ErrKeyspaceNotFound)
	clusterGatewayCli.On("CreateKeySpace", mock.Anything, sharedPool, "ks-test-456", "arn:aws:kms:us-east-1:123456789012:key/test", map[string]string{
		"ExternalId": "60205",
	}).Return(nil)
	clusterGatewayCli.On("SetKeySpaceResourceGroup", mock.Anything, sharedPool, "ks-test-456", mock.Anything).
		Return(nil)
	clusterGatewayCli.On("SetKeySpaceRuVersion", mock.Anything, sharedPool, "ks-test-456", mock.Anything).
		Return(nil)

	action := NewSetKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

	args := actions.ActionArgs{
		workflow.SharedPoolIdKey:  actions.ActionArgString("sp-2"),
		workflow.TiDBClusterIdKey: actions.ActionArgString("ks-test-456"),
		workflow.CmekIdKey:        actions.ActionArgString("arn:aws:kms:us-east-1:123456789012:key/test"),
		workflow.TiDBClusterKey:   actions.ActionArgString(string(tidbClusterStr)),
	}

	require.NoError(t, action.Invoke(args))
}

func TestSetKeySpaceAction_TopologyDriven_DoesNotSkip(t *testing.T) {
	clusterGatewayCli := cgmocks.NewClient(t)
	sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	tidbClusterRepo := repomocks.NewTiDBClusterRepo(t)

	tidbCluster := &apiv1.Cluster{
		Id: "ks-test-topology-worker",
		RequestUnit: &apiv1.Cluster_RequestUnit{
			Max: 1000,
		},
		SharedPoolId: "sp-topology",
		RuVersion:    2,
	}
	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:           "sp-topology",
			CapacityMode: apiv1.CapacityMode_TOPOLOGY_DRIVEN,
		},
	}

	sharedPoolRepo.On("Load", mock.Anything, "sp-topology", false).Return(sharedPool, nil)
	tidbClusterRepo.On("Load", mock.Anything, "ks-test-topology-worker", false).Return(tidbCluster, nil)

	tidbClusterStr, err := protojson.Marshal(tidbCluster)
	require.NoError(t, err)

	clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "ks-test-topology-worker").
		Return(&pdapiv2.KeyspaceMeta{
			KeyspaceMeta: &keyspacepb.KeyspaceMeta{State: keyspacepb.KeyspaceState_ENABLED},
		}, nil)
	clusterGatewayCli.On("SetKeySpaceResourceGroup", mock.Anything, sharedPool, "ks-test-topology-worker", mock.Anything).
		Return(nil)
	clusterGatewayCli.On("SetKeySpaceRuVersion", mock.Anything, sharedPool, "ks-test-topology-worker", mock.Anything).
		Return(nil)

	action := NewSetKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)
	args := actions.ActionArgs{
		workflow.SharedPoolIdKey:  actions.ActionArgString("sp-topology"),
		workflow.TiDBClusterIdKey: actions.ActionArgString("ks-test-topology-worker"),
		workflow.CmekIdKey:        actions.ActionArgString(""),
		workflow.TiDBClusterKey:   actions.ActionArgString(string(tidbClusterStr)),
	}

	require.NoError(t, action.Invoke(args))
}
