package workflow

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/chaos-svc/idl/pbgen/proto/chaos"
	"github.com/tidbcloud/chaos-svc/pkg/domain/constant"
	"github.com/tidbcloud/chaos-svc/pkg/domain/entity"
	"github.com/tidbcloud/chaos-svc/pkg/domain/facade"
	"github.com/tidbcloud/chaos-svc/pkg/domain/facade/mock_facade"
	"github.com/tidbcloud/chaos-svc/pkg/domain/repo/mock_repo"
	"github.com/tidbcloud/pkgv2/bizerr"
)

func TestCreateWorkflow(t *testing.T) {
	type fields struct {
		infraFacade  *mock_facade.MockInfra
		workflowRepo *mock_repo.MockWorkflowRepo
	}
	type args struct {
		params *CreateWorkflowParam
	}
	var (
		testProjectID        uint64 = 1111
		testClusterID        uint64 = 2222
		testUserID           uint64 = 3333
		testWorkflowID       uint64 = 1
		testNetworkName             = "us-west-2"
		testClusterNamespace        = fmt.Sprintf("tidb%d", testClusterID)
		testCluster                 = facade.TiDBCluster{
			ProjectID:    testProjectID,
			ClusterID:    testClusterID,
			NetworkName:  testNetworkName,
			Region:       "us-west-2",
			ProviderType: "eks",
			Namespace:    testClusterNamespace,
		}
		testCreatedAt    = time.Now()
		testWorkflowSpec = entity.WorkflowSpec{
			EntryNodeName: "node1",
			Templates: map[string]entity.WorkflowNodeTemplate{
				"node1": {
					Name:     "node1",
					Type:     constant.WorkflowTemplateTypeSerial,
					Children: []string{"node2"},
				},
				"node2": {
					Name: "node2",
					Type: constant.WorkflowTemplateTypePodShutdown,
					EmbedAction: &entity.EmbedAction{
						PodShutdown: &entity.PodShutdownAction{
							Target: chaos.ExperimentTarget{
								Mode:      chaos.TargetMode_TARGET_MODE_RANDOM,
								Component: chaos.TargetComponent_TARGET_COMPONENT_TIDB,
							},
						},
					},
				},
			},
		}
	)
	tests := []struct {
		name    string
		prepare func(f *fields)
		args    args
		want    *entity.Workflow
		wantErr bool
	}{
		{
			name: "create draft workflow, should be ok",
			prepare: func(f *fields) {
				workflow := &entity.Workflow{
					ClusterID:   testClusterID,
					ProjectID:   testProjectID,
					Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT,
					Spec:        testWorkflowSpec,
					CreatedByID: testUserID,
				}
				f.workflowRepo.EXPECT().Save(context.Background(), workflow).DoAndReturn(func(ctx context.Context, workflow *entity.Workflow) bizerr.BizError {
					workflow.ID = testWorkflowID
					workflow.CreatedAt = testCreatedAt
					return nil
				})
			},
			args: args{
				params: &CreateWorkflowParam{
					TiDBCluster: testCluster,
					UserID:      testUserID,
					Draft:       true,
					Spec:        testWorkflowSpec,
				},
			},
			want: &entity.Workflow{
				ID:          testWorkflowID,
				ClusterID:   testClusterID,
				ProjectID:   testProjectID,
				Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT,
				Spec:        testWorkflowSpec,
				CreatedByID: testUserID,
				CreatedAt:   testCreatedAt,
			},
			wantErr: false,
		},
		{
			name: "create workflow running immediately, should be ok",
			prepare: func(f *fields) {
				workflowEntity1 := entity.Workflow{
					ClusterID:   testClusterID,
					ProjectID:   testProjectID,
					Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING,
					Spec:        testWorkflowSpec,
					CreatedByID: testUserID,
				}
				f.workflowRepo.EXPECT().Save(context.Background(), &workflowEntity1).DoAndReturn(func(ctx context.Context, workflow *entity.Workflow) bizerr.BizError {
					workflow.ID = testWorkflowID
					workflow.CreatedAt = testCreatedAt
					return nil
				})

				workflowEntity2 := workflowEntity1
				workflowEntity2.ID = testWorkflowID
				workflowEntity2.CreatedAt = testCreatedAt
				f.infraFacade.EXPECT().CreateWorkflowIgnoreExist(context.Background(), &workflowEntity2).Return(nil)
			},
			args: args{
				params: &CreateWorkflowParam{
					TiDBCluster: testCluster,
					UserID:      testUserID,
					Draft:       false,
					Spec:        testWorkflowSpec,
				},
			},
			want: &entity.Workflow{
				ID:          testWorkflowID,
				ClusterID:   testClusterID,
				ProjectID:   testProjectID,
				Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING,
				Spec:        testWorkflowSpec,
				CreatedByID: testUserID,
				CreatedAt:   testCreatedAt,
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			f := fields{
				infraFacade:  mock_facade.NewMockInfra(ctrl),
				workflowRepo: mock_repo.NewMockWorkflowRepo(ctrl),
			}
			if tt.prepare != nil {
				tt.prepare(&f)
			}

			svc := NewCreateWorkflowSvc(f.workflowRepo, f.infraFacade)
			got, err := svc.Execute(context.Background(), tt.args.params)
			if (err != nil) != tt.wantErr {
				t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			assert.Equal(t, got, tt.want, "Execute() got = %v, want %v", got, tt.want)
		})
	}
}
