package task

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

	maintent "github.com/tidbcloud/devops/entity/maintenance"

	"github.com/onsi/gomega"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"go.uber.org/mock/gomock"

	baseapi "github.com/tidbcloud/devops/api"
	k8sent "github.com/tidbcloud/devops/entity/k8supgrade"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	cmxapi "github.com/tidbcloud/devops/pkg/service/clustermaintenance/api"
	"github.com/tidbcloud/devops/pkg/service/k8supgrade/infrainfo"
	"github.com/tidbcloud/devops/pkg/service/k8supgrade/task/repo"
)

func Test_svc_syncCanceledTask(t *testing.T) {
	tests := []struct {
		name        string
		task        *k8sent.Task
		k8sClusters []*k8sent.K8sCluster
		mockCalls   func(api *cmxapi.MockServiceAPI, infraInfo *infrainfo.MockServiceAPI)
		expect      func(*gomega.WithT, []*k8sent.K8sCluster)
	}{
		{
			name: "no k8s clusters",
			task: &k8sent.Task{ID: 1, Status: k8sent.TaskStatusCancelled},
		},
		{
			name: "pending window and deleted CR",
			task: &k8sent.Task{ID: 1, Status: k8sent.TaskStatusCancelled},
			k8sClusters: []*k8sent.K8sCluster{
				{
					MaintenanceWindowID: pointer.Pointer(uint64(3)),
					UpgradeOperationCR:  testCRString,
					CloudProvider:       "aws",
					Region:              "us-west-1",
				},
			},
			mockCalls: func(api *cmxapi.MockServiceAPI, infraInfo *infrainfo.MockServiceAPI) {
				api.EXPECT().GetMaintenance(gomock.Any(), uint64(3), cmxapi.GetMaintenanceParam{WithExecutions: true}).Return(&maintent.ClusterMaintenance{
					ClusterMaintenanceInRepo: maintent.ClusterMaintenanceInRepo{ID: 3},
					Task: maintent.ClusterMaintenanceTask{
						ID:           28,
						ProjectID:    11111,
						ScheduleTime: time.Now(),
						Deadline:     &time.Time{},
						Status:       maintent.ClusterMaintenanceTaskStatusPending,
						Executions: []maintent.ClusterMaintenanceExecution{
							{
								ID:        0,
								TaskID:    28,
								ClusterID: 123,
								Status:    maintent.ClusterMaintenanceExecutionStatusPending,
							},
						},
					},
				}, nil)
				api.EXPECT().CancelMaintenanceExecution(gomock.Any(), uint64(3), uint64(0)).Return(nil)
				api.EXPECT().CloseMaintenance(gomock.Any(), uint64(3)).Return(nil)
				infraInfo.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(&k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseNone),
					},
				}, nil)
				infraInfo.EXPECT().DeleteUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil)
			},
			expect: func(g *gomega.WithT, clusters []*k8sent.K8sCluster) {
				g.Expect(len(clusters)).To(gomega.Equal(1))
				g.Expect(clusters[0].UpgradeOperationStatus).To(gomega.Equal("Deleted"))
			},
		},
		{
			name: "running CR and failed to pause",
			task: &k8sent.Task{ID: 1, Status: k8sent.TaskStatusCancelled},
			k8sClusters: []*k8sent.K8sCluster{
				{
					MaintenanceWindowID: pointer.Pointer(uint64(3)),
					UpgradeOperationCR:  testCRString,
					CloudProvider:       "aws",
					Region:              "us-west-1",
				},
			},
			mockCalls: func(api *cmxapi.MockServiceAPI, infraInfo *infrainfo.MockServiceAPI) {
				api.EXPECT().GetMaintenance(gomock.Any(), uint64(3), cmxapi.GetMaintenanceParam{WithExecutions: true}).Return(&maintent.ClusterMaintenance{
					ClusterMaintenanceInRepo: maintent.ClusterMaintenanceInRepo{ID: 3},
					Task: maintent.ClusterMaintenanceTask{
						ID:           28,
						ProjectID:    11111,
						ScheduleTime: time.Now(),
						Deadline:     &time.Time{},
						Status:       maintent.ClusterMaintenanceTaskStatusPending,
						Executions: []maintent.ClusterMaintenanceExecution{
							{
								ID:        0,
								TaskID:    28,
								ClusterID: 123,
								Status:    maintent.ClusterMaintenanceExecutionStatusPending,
							},
						},
					},
				}, nil)
				api.EXPECT().CancelMaintenanceExecution(gomock.Any(), uint64(3), uint64(0)).Return(nil)
				api.EXPECT().CloseMaintenance(gomock.Any(), uint64(3)).Return(nil)
				infraInfo.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(&k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseRunning),
					},
				}, nil)
				infraInfo.EXPECT().PauseUpgradeOperation(gomock.Any(), gomock.Any()).Return(fmt.Errorf("pause error"))
			},
			expect: func(g *gomega.WithT, clusters []*k8sent.K8sCluster) {
				g.Expect(len(clusters)).To(gomega.Equal(1))
				g.Expect(clusters[0].UpgradeOperationStatus).To(gomega.Equal(""))
			},
		},
		{
			name: "CR not found",
			task: &k8sent.Task{ID: 1, Status: k8sent.TaskStatusCancelled},
			k8sClusters: []*k8sent.K8sCluster{
				{
					MaintenanceWindowID: pointer.Pointer(uint64(3)),
					UpgradeOperationCR:  testCRString,
					CloudProvider:       "aws",
					Region:              "us-west-1",
				},
			},
			mockCalls: func(api *cmxapi.MockServiceAPI, infraInfo *infrainfo.MockServiceAPI) {
				api.EXPECT().GetMaintenance(gomock.Any(), uint64(3), cmxapi.GetMaintenanceParam{WithExecutions: true}).Return(&maintent.ClusterMaintenance{
					ClusterMaintenanceInRepo: maintent.ClusterMaintenanceInRepo{ID: 3},
					Task: maintent.ClusterMaintenanceTask{
						ID:           28,
						ProjectID:    11111,
						ScheduleTime: time.Now(),
						Deadline:     &time.Time{},
						Status:       maintent.ClusterMaintenanceTaskStatusPending,
						Executions: []maintent.ClusterMaintenanceExecution{
							{
								ID:        0,
								TaskID:    28,
								ClusterID: 123,
								Status:    maintent.ClusterMaintenanceExecutionStatusPending,
							},
						},
					},
				}, nil)
				api.EXPECT().CancelMaintenanceExecution(gomock.Any(), uint64(3), uint64(0)).Return(nil)
				api.EXPECT().CloseMaintenance(gomock.Any(), uint64(3)).Return(nil)
				infraInfo.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil, errs.APIErrorNew(baseapi.ErrCodeNotFound, fmt.Errorf("")))
			},
			expect: func(g *gomega.WithT, clusters []*k8sent.K8sCluster) {
				g.Expect(len(clusters)).To(gomega.Equal(1))
				g.Expect(clusters[0].UpgradeOperationStatus).To(gomega.Equal("NotFound"))
			},
		},
		{
			name: "failed to close maintenance window",
			task: &k8sent.Task{ID: 1, Status: k8sent.TaskStatusCancelled},
			k8sClusters: []*k8sent.K8sCluster{
				{
					MaintenanceWindowID: pointer.Pointer(uint64(3)),
					UpgradeOperationCR:  testCRString,
					CloudProvider:       "aws",
					Region:              "us-west-1",
				},
			},
			mockCalls: func(api *cmxapi.MockServiceAPI, infraInfo *infrainfo.MockServiceAPI) {
				api.EXPECT().GetMaintenance(gomock.Any(), uint64(3), cmxapi.GetMaintenanceParam{WithExecutions: true}).Return(&maintent.ClusterMaintenance{
					ClusterMaintenanceInRepo: maintent.ClusterMaintenanceInRepo{ID: 3},
					Task: maintent.ClusterMaintenanceTask{
						ID:           28,
						ProjectID:    11111,
						ScheduleTime: time.Now(),
						Deadline:     &time.Time{},
						Status:       maintent.ClusterMaintenanceTaskStatusPending,
						Executions: []maintent.ClusterMaintenanceExecution{
							{
								ID:        0,
								TaskID:    28,
								ClusterID: 123,
								Status:    maintent.ClusterMaintenanceExecutionStatusPending,
							},
						},
					},
				}, nil)
				api.EXPECT().CancelMaintenanceExecution(gomock.Any(), uint64(3), uint64(0)).Return(nil)
				api.EXPECT().CloseMaintenance(gomock.Any(), uint64(3)).Return(fmt.Errorf("close error"))
				infraInfo.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(&k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseNone),
					},
				}, nil)
				infraInfo.EXPECT().DeleteUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil)
			},
			expect: func(g *gomega.WithT, clusters []*k8sent.K8sCluster) {
				g.Expect(len(clusters)).To(gomega.Equal(1))
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockRepo := repo.NewMockRepo(ctrl)
			mockMaintenanceWindowSvc := cmxapi.NewMockServiceAPI(ctrl)
			mockInfraInfoSvc := infrainfo.NewMockServiceAPI(ctrl)

			mockRepo.EXPECT().ListK8sClusters(gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.k8sClusters, nil)
			if tt.mockCalls != nil {
				tt.mockCalls(mockMaintenanceWindowSvc, mockInfraInfoSvc)
			}

			s := &svc{
				taskRepo:             mockRepo,
				maintenanceWindowSvc: mockMaintenanceWindowSvc,
				infraInfoSvc:         mockInfraInfoSvc,
			}
			s.syncCanceledTask(context.TODO(), tt.task.ID)
			if tt.expect != nil {
				g := gomega.NewGomegaWithT(t)
				tt.expect(g, tt.k8sClusters)
			}
		})
	}
}

func Test_svc_pauseOrDeleteUpgradeOperation(t *testing.T) {
	validYAMLContent := `
namespace: test-ns
name: test-name
`
	tests := []struct {
		name           string
		cluster        *k8sent.K8sCluster
		mockCalls      func(*infrainfo.MockServiceAPI)
		expectUOStatus string
		wantErr        bool
	}{
		{
			name:           "no upgrade operation CR",
			cluster:        &k8sent.K8sCluster{},
			expectUOStatus: uoStatusNotFound,
		},
		{
			name: "invalid UpgradeOperationCR YAML",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: "invalid yaml content",
			},
			wantErr: true,
		},
		{
			name: "GetUpgradeOperation returns not found",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
				CloudProvider:      "aws",
				Region:             "us-west-1",
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				req := &infrainfo.CommonUpgradeOperationReq{
					Namespace:     "test-ns",
					Name:          "test-name",
					CloudProvider: "aws",
					Region:        "us-west-1",
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), req).Return(nil, errs.APIErrorNew(baseapi.ErrCodeNotFound, fmt.Errorf("")))
			},
			expectUOStatus: uoStatusNotFound,
		},
		{
			name: "GetUpgradeOperation returns other error",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error"))
			},
			wantErr: true,
		},
		{
			name: "currentUO status is nil",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(&k8sent.UpgradeOperation{}, nil)
			},
			expectUOStatus: uoStatusNone,
			wantErr:        false,
		},
		{
			name: "phase None, delete success",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseNone),
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
				m.EXPECT().DeleteUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil)
			},
			expectUOStatus: uoStatusDeleted,
			wantErr:        false,
		},
		{
			name: "phase Pending, delete success",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhasePending),
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
				m.EXPECT().DeleteUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil)
			},
			expectUOStatus: uoStatusDeleted,
			wantErr:        false,
		},
		{
			name: "phase None, delete fails",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseNone),
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
				m.EXPECT().DeleteUpgradeOperation(gomock.Any(), gomock.Any()).Return(fmt.Errorf("delete error"))
			},
			expectUOStatus: "",
			wantErr:        true,
		},
		{
			name: "phase Running, pause success",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
				CloudProvider:      "aws",
				Region:             "us-west-1",
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseRunning),
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
				m.EXPECT().PauseUpgradeOperation(gomock.Any(), gomock.Any()).Return(nil)
			},
			expectUOStatus: uoStatusPaused,
			wantErr:        false,
		},
		{
			name: "phase Running, pause fails",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
				CloudProvider:      "aws",
				Region:             "us-west-1",
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: string(cloudv1.UpgradeOperationPhaseRunning),
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
				m.EXPECT().PauseUpgradeOperation(gomock.Any(), gomock.Any()).Return(fmt.Errorf("pause error"))
			},
			expectUOStatus: "",
			wantErr:        true,
		},
		{
			name: "unknown phase",
			cluster: &k8sent.K8sCluster{
				UpgradeOperationCR: validYAMLContent,
				CloudProvider:      "aws",
				Region:             "us-west-1",
			},
			mockCalls: func(m *infrainfo.MockServiceAPI) {
				currentUO := &k8sent.UpgradeOperation{
					Status: &k8sent.UpgradeOperationStatus{
						Phase: "Completed",
					},
				}
				m.EXPECT().GetUpgradeOperation(gomock.Any(), gomock.Any()).Return(currentUO, nil)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockSvc := infrainfo.NewMockServiceAPI(ctrl)
			if tt.mockCalls != nil {
				tt.mockCalls(mockSvc)
			}

			s := &svc{infraInfoSvc: mockSvc}
			if err := s.pauseOrDeleteUpgradeOperation(context.TODO(), tt.cluster); (err != nil) != tt.wantErr {
				t.Errorf("pauseOrDeleteUpgradeOperation() error = %v, wantErr %v", err, tt.wantErr)
			}
			if tt.cluster.UpgradeOperationStatus != tt.expectUOStatus {
				t.Errorf("pauseOrDeleteUpgradeOperation() UOStatus = %v, want %v", tt.cluster.UpgradeOperationStatus, tt.expectUOStatus)
			}
		})
	}
}
