package action

import (
	"errors"
	"testing"

	"github.com/pingcap/kvproto/pkg/keyspacepb"
	"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"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
	clustergatewaymocks "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	repomnocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"github.com/tikv/pd/server/apiv2/handlers"
)

func TestDeleteKeySpaceAction_Name(t *testing.T) {
	sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
	tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
	clusterGatewayCli := clustergatewaymocks.NewClient(t)
	action := NewDeleteKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

	expected := DeleteKeySpaceActionName
	assert.Equal(t, expected, action.Name())
}

func TestDeleteKeySpaceAction_Invoke(t *testing.T) {
	tests := []struct {
		name          string
		sharedPoolId  string
		tidbClusterId string
		keyspaceState keyspacepb.KeyspaceState
		setupMocks    func(*repomnocks.SharedPoolRepo, *repomnocks.TiDBClusterRepo, *clustergatewaymocks.Client)
		expectError   bool
		errorContains string
		description   string
	}{
		{
			name:          "enabled keyspace - should transition to disabled",
			sharedPoolId:  "pool-123",
			tidbClusterId: "keyspace-123",
			keyspaceState: keyspacepb.KeyspaceState_ENABLED,
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-123",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-123", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-123",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-123", true).Return(tidbCluster, nil)

				// Mock keyspace in enabled state
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-123").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ENABLED,
					},
				}, nil)

				// Mock successful state update to disabled
				clusterGatewayCli.On("UpdateKeySpaceState", mock.Anything, sharedPool, "keyspace-123", keyspacepb.KeyspaceState_DISABLED).Return(nil)
			},
			expectError:   true,
			errorContains: "keyspace keyspace-123 is not disabled, please try again later",
			description:   "Should transition enabled keyspace to disabled and return error to retry",
		},
		{
			name:          "disabled keyspace - should transition to archived",
			sharedPoolId:  "pool-456",
			tidbClusterId: "keyspace-456",
			keyspaceState: keyspacepb.KeyspaceState_DISABLED,
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-456",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-456", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-456",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-456", true).Return(tidbCluster, nil)

				// Mock keyspace in disabled state
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-456").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_DISABLED,
					},
				}, nil)

				// Mock successful state update to archived
				clusterGatewayCli.On("UpdateKeySpaceState", mock.Anything, sharedPool, "keyspace-456", keyspacepb.KeyspaceState_ARCHIVED).Return(nil)
			},
			expectError:   true,
			errorContains: "keyspace keyspace-456 is not archived, please try again later",
			description:   "Should transition disabled keyspace to archived and return error to retry",
		},
		{
			name:          "archived keyspace - should succeed",
			sharedPoolId:  "pool-789",
			tidbClusterId: "keyspace-789",
			keyspaceState: keyspacepb.KeyspaceState_ARCHIVED,
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-789",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-789", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-789",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-789", true).Return(tidbCluster, nil)

				// Mock keyspace already archived
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-789").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ARCHIVED,
					},
				}, nil)
			},
			expectError: false,
			description: "Should succeed when keyspace is already archived",
		},
		{
			name:          "keyspace not found - should succeed",
			sharedPoolId:  "pool-404",
			tidbClusterId: "keyspace-404",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-404",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-404", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-404",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-404", true).Return(tidbCluster, nil)

				// Mock keyspace not found
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-404").Return(nil, clustergateway.ErrKeyspaceNotFound)
			},
			expectError: false,
			description: "Should succeed when keyspace is not found",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
			clusterGatewayCli := clustergatewaymocks.NewClient(t)

			// Setup mocks
			tt.setupMocks(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Create action
			action := NewDeleteKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Prepare arguments
			args := actions.ActionArgs{
				workflow.SharedPoolIdKey:  actions.ActionArgString(tt.sharedPoolId),
				workflow.TiDBClusterIdKey: actions.ActionArgString(tt.tidbClusterId),
			}

			// Execute test
			err := action.Invoke(args)

			// Validate results
			if tt.expectError {
				require.Error(t, err)
				if tt.errorContains != "" {
					assert.Contains(t, err.Error(), tt.errorContains)
				}
			} else {
				require.NoError(t, err, tt.description)
			}
		})
	}
}

func TestDeleteKeySpaceAction_Invoke_InvalidArgs(t *testing.T) {
	tests := []struct {
		name        string
		args        actions.ActionArgs
		setupMocks  func(*repomnocks.SharedPoolRepo, *repomnocks.TiDBClusterRepo, *clustergatewaymocks.Client)
		expectError bool
		errorMsg    string
		description string
	}{
		{
			name: "missing shared pool id key",
			args: actions.ActionArgs{
				workflow.TiDBClusterIdKey: actions.ActionArgString("cluster-123"),
			},
			setupMocks: func(*repomnocks.SharedPoolRepo, *repomnocks.TiDBClusterRepo, *clustergatewaymocks.Client) {
				// No mocks needed for early validation
			},
			expectError: true,
			errorMsg:    "failed to get sharedPool id",
			description: "Should return error when shared pool ID key is missing",
		},
		{
			name: "missing tidb cluster id key",
			args: actions.ActionArgs{
				workflow.SharedPoolIdKey: actions.ActionArgString("pool-123"),
			},
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-123",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-123", false).Return(sharedPool, nil)
			},
			expectError: true,
			errorMsg:    "failed to get tidb cluster id",
			description: "Should return error when TiDB cluster ID key is missing",
		},
		{
			name: "empty shared pool id value",
			args: actions.ActionArgs{
				workflow.SharedPoolIdKey:  actions.ActionArgString(""),
				workflow.TiDBClusterIdKey: actions.ActionArgString("cluster-123"),
			},
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPoolRepo.On("Load", mock.Anything, "", false).Return(nil, errors.New("empty id"))
			},
			expectError: true,
			errorMsg:    "failed to load sharedPool",
			description: "Should return error when shared pool ID value is empty",
		},
		{
			name: "empty tidb cluster id value",
			args: actions.ActionArgs{
				workflow.SharedPoolIdKey:  actions.ActionArgString("pool-123"),
				workflow.TiDBClusterIdKey: actions.ActionArgString(""),
			},
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-123",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-123", false).Return(sharedPool, nil)
				tidbClusterRepo.On("Load", mock.Anything, "", true).Return(nil, errors.New("empty id"))
			},
			expectError: true,
			errorMsg:    "failed to load tidb cluster",
			description: "Should return error when TiDB cluster ID value is empty",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
			clusterGatewayCli := clustergatewaymocks.NewClient(t)

			// Setup mocks
			tt.setupMocks(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Create action
			action := NewDeleteKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Execute test
			err := action.Invoke(tt.args)

			// Validate results
			if tt.expectError {
				require.Error(t, err)
				if tt.errorMsg != "" {
					assert.Contains(t, err.Error(), tt.errorMsg)
				}
			} else {
				require.NoError(t, err, tt.description)
			}
		})
	}
}

func TestDeleteKeySpaceAction_Invoke_EdgeCases(t *testing.T) {
	tests := []struct {
		name          string
		sharedPoolId  string
		tidbClusterId string
		setupMocks    func(*repomnocks.SharedPoolRepo, *repomnocks.TiDBClusterRepo, *clustergatewaymocks.Client)
		expectError   bool
		errorMsg      string
		description   string
	}{
		{
			name:          "very long keyspace name",
			sharedPoolId:  "pool-123",
			tidbClusterId: "very-long-keyspace-name-that-exceeds-normal-length-limits",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-123",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-123", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "very-long-keyspace-name-that-exceeds-normal-length-limits",
				}
				tidbClusterRepo.On("Load", mock.Anything, "very-long-keyspace-name-that-exceeds-normal-length-limits", true).Return(tidbCluster, nil)

				// Mock keyspace already archived
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "very-long-keyspace-name-that-exceeds-normal-length-limits").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ARCHIVED,
					},
				}, nil)
			},
			expectError: false,
			description: "Should handle very long keyspace name",
		},
		{
			name:          "special characters in keyspace name",
			sharedPoolId:  "pool-456",
			tidbClusterId: "keyspace@123#456",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-456",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-456", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace@123#456",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace@123#456", true).Return(tidbCluster, nil)

				// Mock keyspace already archived
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace@123#456").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ARCHIVED,
					},
				}, nil)
			},
			expectError: false,
			description: "Should handle special characters in keyspace name",
		},
		{
			name:          "unicode characters in keyspace name",
			sharedPoolId:  "pool-789",
			tidbClusterId: "keyspace-中文-测试",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-789",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-789", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-中文-测试",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-中文-测试", true).Return(tidbCluster, nil)

				// Mock keyspace already archived
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-中文-测试").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ARCHIVED,
					},
				}, nil)
			},
			expectError: false,
			description: "Should handle unicode characters in keyspace name",
		},
		{
			name:          "cluster gateway error during get keyspace",
			sharedPoolId:  "pool-error",
			tidbClusterId: "keyspace-error",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-error",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-error", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-error",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-error", true).Return(tidbCluster, nil)

				// Mock cluster gateway error
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-error").Return(nil, errors.New("internal server error"))
			},
			expectError: true,
			errorMsg:    "failed to get keyspace: internal server error",
			description: "Should return error when cluster gateway fails",
		},
		{
			name:          "update keyspace state fails",
			sharedPoolId:  "pool-update-error",
			tidbClusterId: "keyspace-update-error",
			setupMocks: func(sharedPoolRepo *repomnocks.SharedPoolRepo, tidbClusterRepo *repomnocks.TiDBClusterRepo, clusterGatewayCli *clustergatewaymocks.Client) {
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "pool-update-error",
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "pool-update-error", false).Return(sharedPool, nil)

				tidbCluster := &apiv1.Cluster{
					Id: "keyspace-update-error",
				}
				tidbClusterRepo.On("Load", mock.Anything, "keyspace-update-error", true).Return(tidbCluster, nil)

				// Mock keyspace in enabled state
				clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-update-error").Return(&handlers.KeyspaceMeta{
					KeyspaceMeta: &keyspacepb.KeyspaceMeta{
						State: keyspacepb.KeyspaceState_ENABLED,
					},
				}, nil)

				// Mock failed state update
				clusterGatewayCli.On("UpdateKeySpaceState", mock.Anything, sharedPool, "keyspace-update-error", keyspacepb.KeyspaceState_DISABLED).Return(errors.New("update failed"))
			},
			expectError: true,
			errorMsg:    "failed to update keyspace state to disabled: update failed",
			description: "Should return error when update keyspace state fails",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
			clusterGatewayCli := clustergatewaymocks.NewClient(t)

			// Setup mocks
			tt.setupMocks(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Create action
			action := NewDeleteKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)

			// Prepare arguments
			args := actions.ActionArgs{
				workflow.SharedPoolIdKey:  actions.ActionArgString(tt.sharedPoolId),
				workflow.TiDBClusterIdKey: actions.ActionArgString(tt.tidbClusterId),
			}

			// Execute test
			err := action.Invoke(args)

			// Validate results
			if tt.expectError {
				require.Error(t, err)
				if tt.errorMsg != "" {
					assert.Contains(t, err.Error(), tt.errorMsg)
				}
			} else {
				require.NoError(t, err, tt.description)
			}
		})
	}
}

func TestDeleteKeySpaceAction_Invoke_TopologyDriven_DoesNotSkip(t *testing.T) {
	sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
	tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
	clusterGatewayCli := clustergatewaymocks.NewClient(t)

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:           "pool-topology",
			CapacityMode: apiv1.CapacityMode_TOPOLOGY_DRIVEN,
		},
	}
	tidbCluster := &apiv1.Cluster{Id: "keyspace-topology"}

	sharedPoolRepo.On("Load", mock.Anything, "pool-topology", false).Return(sharedPool, nil)
	tidbClusterRepo.On("Load", mock.Anything, "keyspace-topology", true).Return(tidbCluster, nil)
	clusterGatewayCli.On("GetKeySpace", mock.Anything, sharedPool, "keyspace-topology").Return(&handlers.KeyspaceMeta{
		KeyspaceMeta: &keyspacepb.KeyspaceMeta{State: keyspacepb.KeyspaceState_ARCHIVED},
	}, nil)

	action := NewDeleteKeySpaceAction(sharedPoolRepo, tidbClusterRepo, clusterGatewayCli)
	args := actions.ActionArgs{
		workflow.SharedPoolIdKey:  actions.ActionArgString("pool-topology"),
		workflow.TiDBClusterIdKey: actions.ActionArgString("keyspace-topology"),
	}

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