package task

import (
	"context"
	"os"
	"strings"
	"testing"

	"github.com/go-logr/logr"
	. "github.com/onsi/gomega"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/stretchr/testify/assert"
	scheduling_v1 "k8s.io/api/scheduling/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

func TestExtractBackupFailedErrorForAnnotation(t *testing.T) {
	t.Run("prefer top level failed condition", func(t *testing.T) {
		backup := &opv1alpha1.Backup{
			Status: opv1alpha1.BackupStatus{
				Conditions: []opv1alpha1.BackupCondition{
					{
						Type:    opv1alpha1.BackupFailed,
						Status:  "True",
						Reason:  "BackupFailed",
						Message: `prefix Error:create storage failed: bucket mismatch`,
					},
				},
			},
		}
		assert.Equal(t,
			"reason=BackupFailed; message=create storage failed: bucket mismatch",
			extractBackupFailedErrorForAnnotation(backup),
		)
	})

	t.Run("fallback to log sub command failed condition", func(t *testing.T) {
		backup := &opv1alpha1.Backup{
			Status: opv1alpha1.BackupStatus{
				LogSubCommandStatuses: map[opv1alpha1.LogSubCommandType]opv1alpha1.LogSubCommandStatus{
					opv1alpha1.LogStopCommand: {
						Conditions: []opv1alpha1.BackupCondition{
							{
								Type:    opv1alpha1.BackupFailed,
								Status:  "True",
								Reason:  "StopLogBackupFailed",
								Message: `foo error="stop log backup failed: context deadline exceeded" bar`,
							},
						},
					},
				},
			},
		}
		assert.Equal(t,
			"reason=StopLogBackupFailed; message=stop log backup failed: context deadline exceeded",
			extractBackupFailedErrorForAnnotation(backup),
		)
	})

	t.Run("truncate to 4000 bytes", func(t *testing.T) {
		backup := &opv1alpha1.Backup{
			Status: opv1alpha1.BackupStatus{
				Conditions: []opv1alpha1.BackupCondition{
					{
						Type:    opv1alpha1.BackupFailed,
						Status:  "True",
						Reason:  "BackupFailed",
						Message: strings.Repeat("a", 5000),
					},
				},
			},
		}
		got := extractBackupFailedErrorForAnnotation(backup)
		assert.LessOrEqual(t, len(got), maxBackupFailedErrorAnnotationBytes)
		assert.True(t, strings.HasPrefix(got, "reason=BackupFailed; message="))
	})
}

func TestCreate(t *testing.T) {
	type args struct {
		backupYAMLPath string
		cloud          string
	}
	tests := []struct {
		name     string
		args     args
		wantYAML string
		wantErr  bool
	}{
		{
			name: "aws-snapshot-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/aws-snapshot-backup-infra.yaml",
				cloud:          "aws",
			},
			wantYAML: "testdata/backup_expected/aws-snapshot-backup-operator.yaml",
			wantErr:  false,
		},
		{
			name: "aws-log-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/aws-log-backup-infra.yaml",
				cloud:          "aws",
			},
			wantYAML: "testdata/backup_expected/aws-log-backup-operator.yaml",
			wantErr:  false,
		},
		{
			name: "azure-snapshot-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/azure-snapshot-backup-infra.yaml",
				cloud:          "azure",
			},
			wantYAML: "testdata/backup_expected/azure-snapshot-backup-operator.yaml",
			wantErr:  false,
		},
		{
			name: "azure-log-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/azure-log-backup-infra.yaml",
				cloud:          "azure",
			},
			wantYAML: "testdata/backup_expected/azure-log-backup-operator.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-snapshot-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/gcp-snapshot-backup-infra.yaml",
				cloud:          "gcp",
			},
			wantYAML: "testdata/backup_expected/gcp-snapshot-backup-operator.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-log-backup",
			args: args{
				backupYAMLPath: "testdata/backup_from/gcp-log-backup-infra.yaml",
				cloud:          "gcp",
			},
			wantYAML: "testdata/backup_expected/gcp-log-backup-operator.yaml",
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)

			// Create a fake client to mock API calls
			s := runtime.NewScheme()
			g.Expect(opv1alpha1.AddToScheme(s)).Should(Succeed())
			g.Expect(cloudv1.SchemeBuilder.AddToScheme(s)).Should(Succeed())
			g.Expect(scheduling_v1.AddToScheme(s)).Should(Succeed())
			fakeApiserver := fake.NewClientBuilder().WithScheme(s).Build()

			// Create a context
			ctx := context.TODO()

			// Create necessary arguments for NewContext
			eventRecorder := &record.FakeRecorder{}
			logger := logr.Discard()
			localCli := fakeApiserver
			remoteCli := fakeApiserver
			backup := &cloudv1.Backup{}

			backupYaml, err := os.ReadFile(tt.args.backupYAMLPath)
			if (err != nil) != tt.wantErr {
				t.Errorf("Failed to read backup YAML file: %v", err)
				return
			}

			if err == nil {
				if err = yaml.Unmarshal(backupYaml, backup); err != nil {
					t.Fatalf("failed to unmarshal YAML: %v", err)
				}
			}

			cluster := &cloudv1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: backup.Spec.ClusterName,
				},
			}
			var cloudProvider CloudProvider
			switch tt.args.cloud {
			case "aws":
				cloudProvider, err = NewAWSProvider(
					*backup.Spec.Storage,
					"us-west-2",
					"kubernetesName",
					"kubeVersion",
					"nodeRole",
					"accountID",
					"assumeRoleARN",
					"externalID",
					"clusterProviderConfigName",
					"tenantID",
					"projectID",
					cluster.GetClusterID(),
					cloudv1.CloudAWS{},
					&config.RootConfig{},
				)
			case "azure":
				cloudProvider, err = NewAzureProvider(
					"tenantID",
					"subscriptionID",
					"clientID",
					"dataplaneSubscriptionID",
					"kubernetesName",
					"kubeVersion",
					cloudv1.CloudAzure{},
					*backup.Spec.Storage,
					"clusterProviderConfigName",
					"tenantID",
					"projectID",
				)
			case "gcp":
				cloudProvider, err = NewGCPProvider(
					*backup.Spec.Storage,
					"us-west1",
					"test-gcp-project",
					"kubernetesName",
					"kubeVersion",
					"nodeRole",
					"clusterProviderConfigName",
					"tenantID",
					"projectID",
				)
			}
			if (err != nil) != tt.wantErr {
				t.Fatalf("failed to create cloud provider: %v", err)
			}

			// Initialize taskContext with required arguments
			taskContext := NewContext(
				eventRecorder,
				logger,
				localCli,
				remoteCli,
				backup,
				cluster,
				"env",
				cloudProvider,
			)

			// Create a backupTask with necessary fields
			task := &backupTask{
				baseTask: &baseTask{
					Context: taskContext,
				},
			}

			// Call the Create method
			result := task.Create(ctx)

			if tt.wantErr {
				assert.NotEqual(t, deploy.Done(), result)
				return
			}

			// Assert that the result is deploy.Done()
			assert.Equal(t, deploy.Done(), result)

			// Fetch the created Backup object
			opBackup := &opv1alpha1.Backup{}
			err = taskContext.remote.Get(ctx, types.NamespacedName{
				Name:      task.getBackupName(),
				Namespace: task.clusterNamer.GetTiDBNamespace(),
			}, opBackup)
			g.Expect(err).ToNot(HaveOccurred())

			gotYAML, err := yaml.Marshal(opBackup)
			g.Expect(err).ToNot(HaveOccurred())

			// // generate the expected YAML file
			// err = os.WriteFile(tt.wantYAML, gotYAML, 0644)
			// g.Expect(err).ToNot(HaveOccurred(), "Failed to write generated YAML to file")
			// t.Logf("Generated YAML written to %s", tt.wantYAML)

			// Compare the generated YAML with the expected YAML
			expectedYAML, err := os.ReadFile(tt.wantYAML)
			g.Expect(err).ToNot(HaveOccurred(), "Failed to read expected YAML file")
			t.Log(string(gotYAML))
			g.Expect(string(gotYAML)).To(Equal(string(expectedYAML)))
		})
	}
}
