package validate

import (
	"errors"
	"fmt"
	"github.com/pingcap/log"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/operator"
	"github.com/pingcap/tep/pkg/registry/apiserver/tidbcluster"
	"github.com/pingcap/tep/pkg/s3client"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/robfig/cron"
	"go.uber.org/zap"
	"strings"
	"time"
)

const (
	MaxRetries = 20
)

func ValidateName(id string) error {
	if operator.NameRegexp.MatchString(id) {
		return nil
	}

	return fmt.Errorf("%s must match /%s/", id, operator.NameRegexp.String())
}

func ValidateBackupType(backupType string) error {
	if backupType == string(v1alpha1.BackupTypeFull) {
		return nil
	}

	return fmt.Errorf("backupType %s is invalidate", backupType)
}

func ValidateMethod(method string) error {
	if method == string(operator.BackupMethodLogic) {
		return nil
	}

	return fmt.Errorf("method %s is invalidate", method)
}

func ValidateStorageType(storageType string) error {
	if storageType != string(v1alpha1.BackupStorageTypeS3) {
		return errors.New(fmt.Sprintf("storage type %s is invalid", storageType))
	}
	return nil
}

func ValidateNoEmpty(str string, name string) error {
	if strings.TrimSpace(str) == "" {
		return errors.New(fmt.Sprintf("%s can't be empty", name))
	}
	return nil
}

func ValidateS3StorageProvider(s3 *apiserver.S3StorageProvider) error {
	accessKey := s3.AccessKey
	secretKey := s3.SecretKey
	bucket := s3.Bucket
	endpoint := s3.Endpoint
	region := s3.Region
	var err error
	if err = ValidateNoEmpty(accessKey, "accessKey"); err != nil {
		return err
	}

	if err = ValidateNoEmpty(secretKey, "secretKey"); err != nil {
		return err
	}

	if err = ValidateNoEmpty(endpoint, "endpoint"); err != nil {
		return err
	}

	if err = ValidateNoEmpty(bucket, "bucket"); err != nil {
		return err
	}

	if err = ValidateNoEmpty(region, "region"); err != nil {
		return err
	}

	s3Client := s3client.New([]byte(accessKey), []byte(secretKey), endpoint)

	// try to list objects from ceph to validating the accesskey and secretkey
	if _, err := s3Client.ListObjects(bucket, 1, ""); err != nil {
		errString := err.Error()
		log.Info("err info is", zap.String("err", errString))

		if strings.Contains(errString, "RequestError") {
			return errors.New(fmt.Sprintf("endpoint %s is unavailable err: %v", endpoint, err))
		}

		if strings.Contains(errString, "SignatureDoesNotMatch") {
			return errors.New(fmt.Sprintf("accessKey or secretKey is invalid err: %v", err))
		}

		return err
	}

	return nil
}

func ValidateStorageSize(storageSize string) error {
	if operator.StorageSizeRegexp.MatchString(storageSize) {
		return nil
	}

	return fmt.Errorf("%s must match /%s/", storageSize, operator.StorageSizeRegexp.String())
}

func ValidateBackupTemplate(template *apiserver.TiDBClusterBackupTemplate) error {
	var err error
	if err = ValidateNoEmpty(template.StorageClassName, "storageClass"); err != nil {
		return err
	}
	if err = ValidateNoEmpty(template.TiDBSecretName, "secretName"); err != nil {
		return err
	}
	if err = ValidateStorageSize(template.StorageSize); err != nil {
		return err
	}
	if err = tidbcluster.ValidateClusterName(template.Cluster); err != nil {
		return err
	}
	if err = ValidateBackupType(template.Type); err != nil {
		return err
	}
	if err = ValidateMethod(template.Method); err != nil {
		return err
	}

	return ValidateS3StorageProvider(template.StorageProvider.S3)
}

func ValidateBackup(backup *apiserver.TiDBClusterBackup) error {
	return ValidateBackupTemplate(&backup.TiDBClusterBackupTemplate)
}

func ValidateSchedule(schedule string) error {
	var err error
	if _, err = cron.ParseStandard(schedule); err != nil {
		return errors.New(fmt.Sprintf("schedule format is invalid err: %v", err))
	}
	return nil
}

func ValidateReservedTime(duration string) error {
	var err error
	if _, err = time.ParseDuration(duration); err != nil {
		return errors.New(fmt.Sprintf("MaxReservedTime %s format is invalid err: %v", duration, err))
	}
	return nil
}

func ValidatePause(pause *bool) error {
	if pause == nil {
		return errors.New(fmt.Sprintf("pause can't be nil"))
	}
	return nil
}

func ValidateScheduledBackup(scheduledBackup *apiserver.TiDBClusterScheduledBackup) error {
	var err error
	if err = ValidateSchedule(scheduledBackup.Schedule); err != nil {
		return err
	}
	if err = ValidateReservedTime(scheduledBackup.MaxReservedTime); err != nil {
		return err
	}
	if err = ValidatePause(scheduledBackup.Pause); err != nil {
		return err
	}

	return ValidateBackupTemplate(&scheduledBackup.TiDBClusterBackupTemplate)
}

func ValidateRestoreTemplate(restoreTemplate *apiserver.TiDBClusterRestoreTemplate) error {
	var err error
	if err = ValidateNoEmpty(restoreTemplate.StorageClassName, "storageClassName"); err != nil {
		return err
	}
	if err = tidbcluster.ValidateClusterName(restoreTemplate.Cluster); err != nil {
		return err
	}
	if err = ValidateNoEmpty(restoreTemplate.SecretName, "secretName"); err != nil {
		return err
	}
	if err = tidbcluster.ValidateClusterName(restoreTemplate.BackupNamespace); err != nil {
		return err
	}
	return ValidateStorageSize(restoreTemplate.StorageSize)
}

func ValidateRestore(restore *apiserver.TiDBClusterRestore) error {
	return ValidateRestoreTemplate(&restore.TiDBClusterRestoreTemplate)
}

func ValidateMetadataBackup(backup *apiserver.MetadataBackup) error {
	return ValidateMetadataBackupTemplate(&backup.MetadataBackupTemplate)
}

func ValidateMetadataBackupTemplate(template *apiserver.MetadataBackupTemplate) error {
	var err error
	if err = ValidateStorageType(template.StorageType); err != nil {
		return err
	}

	return ValidateS3StorageProvider(template.StorageProvider.S3)
}

func ValidateScheduledMetadataBackup(scheduledBackup *apiserver.MetadataScheduledBackup) error {
	var err error
	if err = ValidateSchedule(scheduledBackup.Schedule); err != nil {
		return err
	}
	if err = ValidateReservedTime(scheduledBackup.MaxReservedTime); err != nil {
		return err
	}
	return ValidateMetadataBackupTemplate(&scheduledBackup.MetadataBackupTemplate)
}
