package backup

import (
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/central-sdk/config"
	"github.com/tidbcloud/central-sdk/db/entity"
	"github.com/tidbcloud/central-sdk/db/tenant"
	runtimemanager "github.com/tidbcloud/central-sdk/runtime/manager"
	"github.com/tidbcloud/central-sdk/worker/operator"
	"github.com/tidbcloud/config-service/sdk/api"
	"github.com/tidbcloud/config-service/sdk/mock"
	"go.uber.org/zap/zaptest"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"
)

// TODO: there is no mock for tenantID and Project
func TestAssembleTiDBBackup(t *testing.T) {
	client := mock.New()
	client.BackupConfigOutput = &api.DescribeBRConfigOutput{
		Version: "v4.0.9",
		Config:  map[string]interface{}{"concurrency": float64(2)},
		Image: api.Image{
			BaseImage: "gcr.io/pingcap-public/dbaas/br",
			Tag:       pointer.StringPtr("v4.0.9-hotfix"),
		},
	}
	ctx := UserContext{
		Model: Model{
			runtimes: &runtimemanager.GardenerRuntimeManager{
				GardenProject: "mock-project",
			},
		},
		ConfigService: client,
		getAwsBaseRoleArn: func() (string, error) {
			return "mock-arn", nil
		},
		tenantAndUser: &tenant.User{
			TenantID: entity.ID(456),
		},
	}
	bg := BgContext(ctx)
	var concurrency uint32 = 2
	cases := []struct {
		Description  string
		Values       operator.TidbBackupVars
		ExpectResult *v1alpha1.Backup
	}{
		{
			Description: "gcp backup",
			Values: operator.TidbBackupVars{
				Bucket:     "mock-bucket",
				BackupName: "mock-bucket-name",
				ClusterID:  entity.ID(123),
				Cloud:      config.Gcp,
			},
			ExpectResult: &v1alpha1.Backup{
				ObjectMeta: v1.ObjectMeta{Name: "mock-bucket-name"},
				Spec: v1alpha1.BackupSpec{
					From: &v1alpha1.TiDBAccessConfig{Port: 4000, User: "root"},
					Type: "full",
					StorageProvider: v1alpha1.StorageProvider{Gcs: &v1alpha1.GcsStorageProvider{
						Bucket:     "mock-bucket",
						SecretName: "gcp-br-credentials",
						Prefix:     "mock-bucket-name",
					}},
					BR:          &v1alpha1.BRConfig{Cluster: "db", Concurrency: &concurrency, SendCredToTikv: pointer.BoolPtr(true)},
					ToolImage:   "gcr.io/pingcap-public/dbaas/br:v4.0.9-hotfix",
					CleanPolicy: "Delete",
				}},
		},
		{
			Description: "aws backup",
			Values: operator.TidbBackupVars{
				Bucket:     "mock-bucket",
				BackupName: "mock-bucket-name",
				ClusterID:  entity.ID(123),
				Cloud:      config.Aws,
			},
			ExpectResult: &v1alpha1.Backup{
				ObjectMeta: v1.ObjectMeta{
					Name: "mock-bucket-name",
					Annotations: map[string]string{
						"iam.amazonaws.com/role": "mock-arn/dbaas-mock-project-456-1362f1d0-bucket-reader",
					},
				},
				Spec: v1alpha1.BackupSpec{

					From: &v1alpha1.TiDBAccessConfig{Port: 4000, User: "root"},
					Type: "full",
					StorageProvider: v1alpha1.StorageProvider{
						S3: &v1alpha1.S3StorageProvider{Provider: "aws", Bucket: "mock-bucket", Prefix: "mock-bucket-name"},
					},
					BR:          &v1alpha1.BRConfig{Cluster: "db", Concurrency: &concurrency, SendCredToTikv: pointer.BoolPtr(false)},
					ToolImage:   "gcr.io/pingcap-public/dbaas/br:v4.0.9-hotfix",
					CleanPolicy: "Delete",
				}},
		},
	}

	for _, tt := range cases {
		t.Run(tt.Description, func(t *testing.T) {
			backupCR, err := bg.assembleTiDBBackup(tt.Values)
			if err != nil {
				t.Fatal(err)
			}
			diff := cmp.Diff(backupCR, tt.ExpectResult)
			if diff != "" {
				t.Fatalf("mismatch (-want +got):\n%s", diff)
			}
		})
	}
}
func TestBackupFinish(t *testing.T) {
	start := time.Now()
	client := mock.New()
	ctx := UserContext{
		Model: Model{
			runtimes: &runtimemanager.GardenerRuntimeManager{
				GardenProject: "mock-project",
			},
		},
		ConfigService: client,
		getAwsBaseRoleArn: func() (string, error) {
			return "mock-arn", nil
		},
		tenantAndUser: &tenant.User{
			TenantID: entity.ID(456),
		},
		log: zaptest.NewLogger(t),
	}
	bgctx := BgContext(ctx)
	backupCR := &v1alpha1.Backup{
		ObjectMeta: v1.ObjectMeta{
			Name: "mock-backupCR-name",
		},
		Status: v1alpha1.BackupStatus{
			Phase: v1alpha1.BackupComplete,
		},
	}
	backup := &entity.Backup{
		FinishedAt: nil,
		Status:     entity.BackupStatus_PENDING,
	}
	assert.Nil(t, backup.FinishedAt)
	// UpdateBackupProgress will panic, so we need to recover it.
	func() {
		defer func() {
			if r := recover(); r != nil {
				return
			}
		}()
		_ = bgctx.UpdateBackupProgress(backupCR, nil, backup)
	}()
	assert.NotNil(t, backup.FinishedAt)
	assert.True(t, backup.FinishedAt.After(start))
}
