package operator

import (
	"github.com/pingcap/log"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/pingcap/tidb-operator/pkg/client/clientset/versioned"
	"go.uber.org/zap"
	corev1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"regexp"
)

const (
	TEPSchedulableLabel           string = "tep.pingcap.com/schedulable"
	TEPManagedLabel               string = "tep.pingcap.com/managed"
	TEPComputePurpose             string = "compute"
	TEPStoragePurpose             string = "storage"
	TEPBothPurpose                string = "storage/compute"
	TEPComputeNodeLabel           string = "tep.pingcap.com/compute"
	TEPStorageNodeLabel           string = "tep.pingcap.com/storage"
	BackupMethodLogic             string = "logic"
	BackupOperationManual         string = "manual"
	BackupOperationAuto           string = "automatic"
	BackupNamePrefix              string = "tidb-backup-manager"
	ScheduledBackupName           string = "tidb-scheduled-backup-manager"
	RestoreNamePrefix             string = "tidb-restore-manager"
	BackupTimeFormat              string = "20060102-150405"
	BackupSecretName              string = "tidb-backup-secret"
	MetadataBackupJob             string = "metadata-backup-job"
	MetadataBackupCronJob         string = "metadata-scheduled-backup-manager"
	MetadataRestoreJob            string = "metadata-restore-job"
	MetadataCleanJob              string = "metadata-clean-job"
	TimeStartedLabel              string = "tep.pingcap.com-timestarted"
	TimeCompletedLabel            string = "tep.pingcap.com-timecompleted"
	BackupSizeLabel               string = "tep.pingcap.com-backupsize"
	TepServiceAccountName         string = "tep-apiserver"
	MetadataBucketName            string = "tep-metadata-backup"
	MaxReservedTimeLabel          string = "tep.pingcap.com-maxreservedtime"
	MetadataBackupComponentLabel  string = "tep.pingcap.com-metadatabackup"
	MetadataRestoreComponentLabel string = "tep.pingcap.com-metadatarestore"
	MetadataCleanComponentLabel   string = "tep.pingcap.com-metadataclean"
	// BackupRootPath is the root path to backup data
	BackupRootPath string = "/backup"
	// DefaultArchiveExtention represent the data archive type
	DefaultArchiveExtention string = ".tgz"
	// fake TCName of metadata mysql cluster
	MetadataTCName string = "metadata-backup"
	// fake config schema name in tep
	DefaultConfigSchemaName = "config-schema"
)

const (
	MaxUint32 uint32 = ^uint32(0)
	MinUint32 uint32 = 0
	MaxInt32  int32  = int32(MaxUint32 >> 1)
	MinInt32  int32  = -MaxInt32 - 1
)

var (
	NameRegexp        = regexp.MustCompile(`^[a-z][a-z0-9]{15}$`)
	StorageSizeRegexp = regexp.MustCompile(`^[0-9]*(K|M|G|P)i$`)
)

type Interface interface {
	CreateTiDBCluster(instance *apiserver.TiDBCluster) error
	UpdateTiDBCluster(instance *apiserver.TiDBCluster) error
	DeleteTiDBCluster(clusterName string) error
	GetTiDBCluster(clusterName string) (*v1alpha1.TidbCluster, error)
}

type client struct {
	clusterClient *versioned.Clientset
}

func NewOperatorClient(operatorClient *versioned.Clientset) *client {
	return &client{
		clusterClient: operatorClient,
	}
}

func (oc *client) CreateTiDBCluster(instance *apiserver.TiDBCluster) error {
	tidbClusterValues := newDefaultTiDBClusterValues()
	err := merge(tidbClusterValues, instance)
	if err != nil {
		return nil
	}

	log.Info("start installing tidb cluster", zap.String("Name", instance.Name))
	_, err = oc.clusterClient.PingcapV1alpha1().TidbClusters(instance.Name).Create(tidbClusterValues)
	if err != nil {
		return err
	}
	log.Info("finish installing tidb cluster", zap.String("Name", instance.Name))

	tm := newDefaultMonitor()
	mergeMonitor(tm, instance)
	log.Info("start installing tidb cluster monitor", zap.String("Name", instance.Name))
	_, err = oc.clusterClient.PingcapV1alpha1().TidbMonitors(instance.Name).Create(tm)
	if err != nil {
		return err
	}
	log.Info("finish installing tidb cluster monitor", zap.String("Name", instance.Name))

	//initializer
	initializer := newInitializer(instance)
	log.Info("start installing tidb cluster initializer", zap.String("Name", instance.Name))
	_, err = oc.clusterClient.PingcapV1alpha1().TidbInitializers(instance.Name).Create(initializer)
	if err != nil {
		return err
	}
	log.Info("finish installing tidb cluster initializer", zap.String("Name", instance.Name))
	return nil
}

func (oc *client) UpdateTiDBCluster(instance *apiserver.TiDBCluster) error {
	tidbClusterValues, err := oc.GetTiDBCluster(instance.Name)
	if err != nil {
		return err
	}

	err = merge(tidbClusterValues, instance)
	if err != nil {
		return nil
	}

	log.Info("update tidb cluster", zap.String("Name", instance.Name))
	_, err = oc.clusterClient.PingcapV1alpha1().TidbClusters(instance.Name).Update(tidbClusterValues)
	if err != nil {
		return err
	}
	log.Info("finish update tidb cluster", zap.String("Name", instance.Name))

	tm, err := oc.clusterClient.PingcapV1alpha1().TidbMonitors(instance.Name).Get(instance.Name, corev1.GetOptions{})
	if err != nil {
		return err
	}
	mergeMonitor(tm, instance)
	log.Info("update tidb cluster monitor", zap.String("Name", instance.Name))
	_, err = oc.clusterClient.PingcapV1alpha1().TidbMonitors(instance.Name).Update(tm)
	if err != nil {
		return err
	}
	log.Info("finish update tidb cluster monitor", zap.String("Name", instance.Name))
	return nil
}

func (oc *client) DeleteTiDBCluster(name string) error {
	err := oc.clusterClient.PingcapV1alpha1().TidbClusters(name).Delete(name, &corev1.DeleteOptions{})
	if err != nil {
		return err
	}
	err = oc.clusterClient.PingcapV1alpha1().TidbInitializers(name).Delete(name, &corev1.DeleteOptions{})
	if err != nil {
		return err
	}
	return oc.clusterClient.PingcapV1alpha1().TidbMonitors(name).Delete(name, &corev1.DeleteOptions{})
}

// GetTiDBCluster gets helm values of tidb cluster release
func (oc *client) GetTiDBCluster(releaseName string) (*v1alpha1.TidbCluster, error) {
	return oc.clusterClient.PingcapV1alpha1().TidbClusters(releaseName).Get(releaseName, corev1.GetOptions{})
}
