package action

import (
	"errors"
	"strings"
	"testing"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	clusteragentv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	clustergateway_mocks "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"
)

func TestRestartTiDBGroupsAction_Start(t *testing.T) {
	tests := []struct {
		name          string
		args          actions.ActionArgs
		setupMocks    func(*repomocks.SharedPoolRepo, *repomocks.TiDBGroupRepo, *clustergateway_mocks.Client)
		expectedError bool
		errorContains string
	}{
		{
			name: "missing shared pool id",
			args: actions.ActionArgs{
				"trace_id": actions.ActionArgString("test-trace"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
			},
			expectedError: true,
			errorContains: "failed to get sharedPool id",
		},
		{
			name: "failed to load shared pool",
			args: actions.ActionArgs{
				"trace_id":               actions.ActionArgString("test-trace"),
				workflow.SharedPoolIdKey: actions.ActionArgString("sp-1"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
				sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(nil, errors.New("not found"))
			},
			expectedError: true,
			errorContains: "failed to load sharedPool",
		},
		{
			name: "failed to list tidb groups",
			args: actions.ActionArgs{
				"trace_id":               actions.ActionArgString("test-trace"),
				workflow.SharedPoolIdKey: actions.ActionArgString("sp-1"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
				sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(&entity.SharedPool{}, nil)
				tidbGroupRepo.On("List", mock.Anything, []string{"sp-1"}, []string(nil), []string(nil), []string(nil), false).Return(nil, errors.New("db error"))
			},
			expectedError: true,
			errorContains: "failed to list tidb groups",
		},
		{
			name: "no tidb groups found - success with no restarts",
			args: actions.ActionArgs{
				"trace_id":               actions.ActionArgString("test-trace"),
				workflow.SharedPoolIdKey: actions.ActionArgString("sp-1"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
				sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(&entity.SharedPool{}, nil)
				tidbGroupRepo.On("List", mock.Anything, []string{"sp-1"}, []string(nil), []string(nil), []string(nil), false).Return([]*entity.TiDBGroup{}, nil)
			},
			expectedError: false,
		},
		{
			name: "successfully restart all tidb groups",
			args: actions.ActionArgs{
				"trace_id":               actions.ActionArgString("test-trace"),
				workflow.SharedPoolIdKey: actions.ActionArgString("sp-1"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
				targetPool := &entity.SharedPool{}
				sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(targetPool, nil)
				tidbGroupRepo.On("List", mock.Anything, []string{"sp-1"}, []string(nil), []string(nil), []string(nil), false).Return([]*entity.TiDBGroup{
					{TiDBGroup: apiv1.TiDBGroup{Id: "tidb-group-1"}},
					{TiDBGroup: apiv1.TiDBGroup{Id: "tidb-group-2"}},
				}, nil)
				cgCli.On("ClusterAgentOpsRestart", mock.Anything, targetPool, mock.MatchedBy(func(req *clusteragentv1.RestartReq) bool {
					return req.ClusterId == "sp-1" &&
						req.TargetType == clusteragentv1.RestartTargetType_COMPONENT &&
						req.TargetName == "tidb" &&
						req.Force == false &&
						req.TargetGroup == "db-tidb-group-1" &&
						strings.HasPrefix(req.OperationId, "restarttidb")
				})).Return(nil)
				cgCli.On("ClusterAgentOpsRestart", mock.Anything, targetPool, mock.MatchedBy(func(req *clusteragentv1.RestartReq) bool {
					return req.TargetGroup == "db-tidb-group-2"
				})).Return(nil)
			},
			expectedError: false,
		},
		{
			name: "restart fails for one group",
			args: actions.ActionArgs{
				"trace_id":               actions.ActionArgString("test-trace"),
				workflow.SharedPoolIdKey: actions.ActionArgString("sp-1"),
			},
			setupMocks: func(sharedPoolRepo *repomocks.SharedPoolRepo, tidbGroupRepo *repomocks.TiDBGroupRepo, cgCli *clustergateway_mocks.Client) {
				targetPool := &entity.SharedPool{}
				sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(targetPool, nil)
				tidbGroupRepo.On("List", mock.Anything, []string{"sp-1"}, []string(nil), []string(nil), []string(nil), false).Return([]*entity.TiDBGroup{
					{TiDBGroup: apiv1.TiDBGroup{Id: "tidb-group-1"}},
					{TiDBGroup: apiv1.TiDBGroup{Id: "tidb-group-2"}},
				}, nil)
				cgCli.On("ClusterAgentOpsRestart", mock.Anything, targetPool, mock.MatchedBy(func(req *clusteragentv1.RestartReq) bool {
					return req.TargetGroup == "db-tidb-group-1"
				})).Return(nil)
				cgCli.On("ClusterAgentOpsRestart", mock.Anything, targetPool, mock.MatchedBy(func(req *clusteragentv1.RestartReq) bool {
					return req.TargetGroup == "db-tidb-group-2"
				})).Return(errors.New("restart failed"))
			},
			expectedError: true,
			errorContains: "failed to restart tidb group tidb-group-2",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
			tidbGroupRepo := repomocks.NewTiDBGroupRepo(t)
			cgCli := clustergateway_mocks.NewClient(t)

			tt.setupMocks(sharedPoolRepo, tidbGroupRepo, cgCli)

			a := &restartTiDBGroupsAction{
				sharedPoolRepo:    sharedPoolRepo,
				tidbGroupRepo:     tidbGroupRepo,
				clustergatewayCli: cgCli,
			}

			err := a.Start(tt.args)
			if tt.expectedError {
				require.Error(t, err)
				if tt.errorContains != "" {
					require.Contains(t, err.Error(), tt.errorContains)
				}
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestRestartTiDBGroupsAction_CheckFinish(t *testing.T) {
	a := &restartTiDBGroupsAction{}
	finished, err := a.CheckFinish(actions.ActionArgs{})
	require.NoError(t, err)
	require.True(t, finished)
}
