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"
)

func TestUpdateWorkflow(t *testing.T) {
	type fields struct {
		infraFacade  *mock_facade.MockInfra
		timeFacade   *mock_facade.MockTime
		workflowRepo *mock_repo.MockWorkflowRepo
	}
	type args struct {
		params *UpdateWorkflowParam
	}
	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,
		}
		testTime         = time.Now()
		testDraft        = false
		testSummary      = "test summary"
		testReportLink   = "http://report-link"
		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: "update summary and report link",
			prepare: func(f *fields) {
				workflow1 := entity.Workflow{
					ClusterID:   testClusterID,
					ProjectID:   testProjectID,
					Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT,
					Spec:        testWorkflowSpec,
					CreatedByID: testUserID,
				}
				f.workflowRepo.EXPECT().Load(context.Background(), testWorkflowID).Return(&workflow1, nil)

				workflow2 := workflow1
				workflow2.Summary = testSummary
				workflow2.ReportLink = testReportLink
				f.workflowRepo.EXPECT().Save(context.Background(), &workflow2).Return(nil)
			},
			args: args{
				params: &UpdateWorkflowParam{
					TiDBCluster: testCluster,
					WorkflowID:  testWorkflowID,
					Summary:     &testSummary,
					ReportLink:  &testReportLink,
				},
			},
			want: &entity.Workflow{
				ClusterID:   testClusterID,
				ProjectID:   testProjectID,
				Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT,
				Spec:        testWorkflowSpec,
				CreatedByID: testUserID,
				Summary:     testSummary,
				ReportLink:  testReportLink,
			},
			wantErr: false,
		},
		{
			name: "update draft status and create workflow in infra",
			prepare: func(f *fields) {
				workflow1 := entity.Workflow{
					ClusterID:   testClusterID,
					ProjectID:   testProjectID,
					Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_DRAFT,
					Spec:        testWorkflowSpec,
					CreatedByID: testUserID,
				}
				f.workflowRepo.EXPECT().Load(context.Background(), testWorkflowID).Return(&workflow1, nil)

				workflow2 := workflow1
				workflow2.Status = chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING
				workflow2.StartedAt = &testTime
				f.workflowRepo.EXPECT().Save(context.Background(), &workflow2).Return(nil)

				f.infraFacade.EXPECT().CreateWorkflowIgnoreExist(context.Background(), &workflow2).Return(nil)

				f.timeFacade.EXPECT().Now().Return(testTime)
			},
			args: args{
				params: &UpdateWorkflowParam{
					TiDBCluster: testCluster,
					WorkflowID:  testWorkflowID,
					Draft:       &testDraft,
				},
			},
			want: &entity.Workflow{
				ClusterID:   testClusterID,
				ProjectID:   testProjectID,
				Status:      chaos.ExperimentStatus_EXPERIMENT_STATUS_RUNNING,
				StartedAt:   &testTime,
				Spec:        testWorkflowSpec,
				CreatedByID: testUserID,
			},
			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),
				timeFacade:   mock_facade.NewMockTime(ctrl),
			}
			if tt.prepare != nil {
				tt.prepare(&f)
			}

			svc := NewUpdateWorkflowSvc(f.workflowRepo, f.infraFacade, f.timeFacade)
			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)
		})
	}
}
