package server

import (
	"context"
	"errors"
	"testing"

	"github.com/elliotchance/pie/v2"
	"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/pkg/clustergateway"
	clustergatewaymock "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	repositorymock "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	km "github.com/tidbcloud/cluster-service-ng/pkg/storage/repoext/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
)

func TestListLightweightBackups(t *testing.T) {
	gwResp := &clustergateway.ListLightweightBackupsResponse{
		Data: []clustergateway.KernelLightweightBackupItem{
			{
				ID: 123,
				Details: clustergateway.BackupItemDetails{
					Keyspaces: []clustergateway.KeyspaceBackupInfo{
						{
							KeyspaceName:  "ks1",
							KeyspaceState: "ENABLED",
						},
					},
				},
			},
			{
				ID: 1234,
				Details: clustergateway.BackupItemDetails{
					Keyspaces: []clustergateway.KeyspaceBackupInfo{
						{
							KeyspaceName:  "ks1",
							KeyspaceState: "ENABLED",
						},
						{
							KeyspaceName:  "ks2",
							KeyspaceState: "ENABLED",
						},
						{
							KeyspaceName:  "ks3",
							KeyspaceState: "DISABLE",
						},
						{
							KeyspaceName:  "DEFAULT",
							KeyspaceState: "ENABLED",
						},
					},
				},
			},
		},
	}

	type fields struct {
		sharedPoolRepo    *repositorymock.SharedPoolRepo
		clustergatewayCli *clustergatewaymock.Client
		keyspaceFetcher   *km.KeyspaceFetcher
	}
	type args struct {
		ctx   context.Context
		input *ListLightweightBackupsInput
	}
	type mocksSetup func(f *fields)

	tests := []struct {
		name       string
		args       args
		setupMocks mocksSetup
		wantErr    string
		wantResp   *ListLightweightBackupsOutput
	}{
		{
			name: "sharedPoolRepo returns error",
			args: args{
				ctx: context.Background(),
				input: &ListLightweightBackupsInput{
					ListLightweightBackupsRequest: &apiv1.ListLightweightBackupsRequest{
						SharedPoolId: "sp-err",
					},
				},
			},
			setupMocks: func(f *fields) {
				f.sharedPoolRepo.On("Load", mock.Anything, "sp-err", false).Return(nil, errors.New("load error"))
			},
			wantErr: "query sharedPool failed: load error",
		},
		{
			name: "clustergateway call fails",
			args: args{
				ctx: context.Background(),
				input: &ListLightweightBackupsInput{
					ListLightweightBackupsRequest: &apiv1.ListLightweightBackupsRequest{
						SharedPoolId: "sp-1",
						FromPrefix:   "fp-1",
						LoadDetails:  true,
						MaxCount:     5,
					},
				},
			},
			setupMocks: func(f *fields) {
				mockSharedPool := &entity.SharedPool{}
				downReq := &clustergateway.ListLightweightBackupsRequest{
					FromPrefix:  "fp-1",
					LoadDetails: true,
					MaxCount:    5,
				}
				f.sharedPoolRepo.On("Load", mock.Anything, "sp-1", false).Return(mockSharedPool, nil)
				f.clustergatewayCli.On("ListLightweightBackups", mock.Anything, mockSharedPool, tioperator.DefaultTiBRName, downReq).
					Return(nil, errors.New("proxy failed"))
			},
			wantErr: "proxy call failed: proxy failed",
		},
		{
			name: "proxy call success, get cluster ids failed",
			args: args{
				ctx: context.Background(),
				input: &ListLightweightBackupsInput{
					ListLightweightBackupsRequest: &apiv1.ListLightweightBackupsRequest{
						SharedPoolId: "sp-2",
						FromPrefix:   "prefix-ok",
						LoadDetails:  false,
						MaxCount:     2,
					},
				},
			},
			setupMocks: func(f *fields) {
				mockSharedPool := &entity.SharedPool{}
				mockSharedPool.ObjectStorage = &apiv1.SharedPool_ObjectStorage{
					Region: "region-ok",
					Bucket: "bucket-ok",
					Prefix: "dfs-ok",
				}
				downReq := &clustergateway.ListLightweightBackupsRequest{
					FromPrefix:  "prefix-ok",
					LoadDetails: false,
					MaxCount:    2,
				}

				f.sharedPoolRepo.On("Load", mock.Anything, "sp-2", false).Return(mockSharedPool, nil)
				f.clustergatewayCli.On("ListLightweightBackups", mock.Anything, mockSharedPool, tioperator.DefaultTiBRName, downReq).
					Return(gwResp, nil)
				f.keyspaceFetcher.On("GetClusterIds", mock.Anything, "sp-2", pie.Sort([]string{"ks1", "ks2"})).
					Return(nil, errors.New("failed"))
			},
			wantErr: "GetClusterIds failed: failed",
		},
		{
			name: "successful call returns expected response",
			args: args{
				ctx: context.Background(),
				input: &ListLightweightBackupsInput{
					ListLightweightBackupsRequest: &apiv1.ListLightweightBackupsRequest{
						SharedPoolId: "sp-2",
						FromPrefix:   "prefix-ok",
						LoadDetails:  false,
						MaxCount:     2,
					},
				},
			},
			setupMocks: func(f *fields) {
				mockSharedPool := &entity.SharedPool{}
				mockSharedPool.ObjectStorage = &apiv1.SharedPool_ObjectStorage{
					Region: "region-ok",
					Bucket: "bucket-ok",
					Prefix: "dfs-ok",
				}
				downReq := &clustergateway.ListLightweightBackupsRequest{
					FromPrefix:  "prefix-ok",
					LoadDetails: false,
					MaxCount:    2,
				}

				f.sharedPoolRepo.On("Load", mock.Anything, "sp-2", false).Return(mockSharedPool, nil)
				f.clustergatewayCli.On("ListLightweightBackups", mock.Anything, mockSharedPool, tioperator.DefaultTiBRName, downReq).
					Return(gwResp, nil)
				f.keyspaceFetcher.On("GetClusterIds", mock.Anything, "sp-2", pie.Sort([]string{"ks1", "ks2"})).
					Return(map[string]string{
						"ks1": "c1",
						"ks2": "c2",
					}, nil)
			},
			wantResp: &ListLightweightBackupsOutput{
				ListLightweightBackupsResponse: *gwResp,
				DfsPrefix:                      "dfs-ok/cse",
				DfsBucket:                      "bucket-ok",
				DfsRegion:                      "region-ok",
				Keyspace2ClusterId: map[string]string{
					"ks1": "c1",
					"ks2": "c2",
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sharedPoolRepo := new(repositorymock.SharedPoolRepo)
			clustergatewayCli := new(clustergatewaymock.Client)
			keyspaceFetcher := new(km.KeyspaceFetcher)

			f := &fields{
				sharedPoolRepo:    sharedPoolRepo,
				clustergatewayCli: clustergatewayCli,
				keyspaceFetcher:   keyspaceFetcher,
			}

			if tt.setupMocks != nil {
				tt.setupMocks(f)
			}

			s := &serverImpl{
				sharedPoolRepo:    f.sharedPoolRepo,
				clustergatewayCli: f.clustergatewayCli,
				keyspaceFetcher:   f.keyspaceFetcher,
			}
			gotResp, err := s.ListLightweightBackups(tt.args.ctx, tt.args.input)

			if tt.wantErr != "" {
				require.Error(t, err)
				require.EqualError(t, err, tt.wantErr)
				require.Nil(t, gotResp)
			} else {
				require.NoError(t, err)
				require.Equal(t, tt.wantResp, gotResp)
			}
		})
	}
}
