package tidbclusterrestore

import (
	"context"
	"fmt"
	"github.com/pingcap/tep/pkg/i18n"
	"github.com/pingcap/tep/pkg/rbac"
	"github.com/pingcap/tep/pkg/utils"
	"time"

	"github.com/pingcap/tep/db/entity"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/operator"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"github.com/pingcap/tep/pkg/utils/validate"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
)

type Storage struct {
	store         *store.Store
	groupResource schema.GroupResource
}

func NewStorage(store *store.Store, groupResource schema.GroupResource) *Storage {
	return &Storage{
		store:         store,
		groupResource: groupResource,
	}
}

func (s *Storage) Kind() string {
	return "TiDBClusterRestore"
}

func (s *Storage) New() runtime.Object {
	return &apiserver.TiDBClusterRestore{}
}

func (s *Storage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	startTime := time.Now()
	operatorName := utils.GetAPIUsername(ctx)
	if !rbac.CanOperate(s.store.DB, operatorName, rbac.OtherClusterOperation) {
		return nil, errors.NewForbidden(s.groupResource, operatorName, fmt.Errorf("permission denied"))
	}
	restore := obj.(*apiserver.TiDBClusterRestore)
	namespace := request.NamespaceValue(ctx)
	var err error
	var backup *v1alpha1.Backup

	if backup, err = s.store.Controller().GetBackup(restore.Backup, restore.BackupNamespace); err != nil {
		if errors.IsNotFound(err) {
			return nil, errors.NewNotFound(s.groupResource, fmt.Sprintf("backup name[%s] is not found in namespace[%s]", restore.Backup, restore.BackupNamespace))
		}
		msg := fmt.Errorf("failed to get backupCR name[%s] in namespace[%s]. err: %v", restore.Backup, restore.BackupNamespace, err)
		return nil, errors.NewInternalError(msg)
	}

	if s.store.Controller().GetBackupStatus(backup) != v1alpha1.BackupComplete {
		msg := fmt.Errorf("backup is uncompleted name[%s] in namespace[%s]", backup.Name, backup.Namespace)
		return nil, errors.NewInternalError(msg)
	}

	// set default secret name
	if restore.SecretName == "" {
		restore.SecretName = operator.BackupSecretName
	}

	if err = validate.ValidateRestore(restore); err != nil {
		return nil, errors.NewBadRequest(err.Error())
	}

	restoreCR := s.store.Controller().AssembleRestore(namespace, backup, restore)

	if restoreCR, err = s.store.Controller().CreateRestore(restoreCR, namespace); err != nil {
		msg := fmt.Errorf("failed to create restoreCR name[%s] in namespace [%s]. err: %v", restoreCR.Name, namespace, err)
		return nil, errors.NewInternalError(msg)
	}
	restore.TiDBClusterRestoreTemplate.Name = restoreCR.ObjectMeta.Name
	restore.ObjectMeta.Name = restoreCR.ObjectMeta.Name
	restore.ObjectMeta.Namespace = restoreCR.ObjectMeta.Namespace

	utils.AddEventAndLogError(s.store.DB, &utils.Event{
		UserID:         operatorName,
		Payload:        restore,
		OperationType:  entity.OperateOperation,
		ManagementType: entity.InstanceManagement,
		CostTime:       time.Now().Sub(startTime).Nanoseconds(),
	})
	i18n.PlatformMessage.AddPlatformMessageAndLogError(s.store.DB, i18n.OperateMessageType, i18n.RestoreBackup, namespace)
	return restore, nil
}

func (s *Storage) NewGetOptions() (runtime.Object, bool, string) {
	return &apiserver.TiDBClusterRestore{}, true, ""
}

func (s *Storage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	namespace := request.NamespaceValue(ctx)
	var err error
	var restore *v1alpha1.Restore
	if restore, err = s.store.Controller().GetRestore(name, namespace); err != nil {
		if errors.IsNotFound(err) {
			return nil, errors.NewNotFound(s.groupResource, fmt.Sprintf("restore name[%s] is not found in namespace[%s]", name, namespace))
		}
		msg := fmt.Errorf("failed to get restoreCR name[%s] in namespace[%s]. err: %v", name, namespace, err)
		return nil, errors.NewInternalError(msg)
	}
	return s.store.Controller().AssembleTiDBClusterRestore(restore), nil
}

func (s *Storage) List(ctx context.Context, options *metainternalversion.ListOptions, extraOptions runtime.Object) (runtime.Object, error) {
	var restoreCRs *v1alpha1.RestoreList
	var restoreList []apiserver.TiDBClusterRestore
	var itemTotal int64
	var err error
	namespace := request.NamespaceValue(ctx)
	extraOption := extraOptions.(*apiserver.BackupListOptions)
	if restoreCRs, err = s.store.Controller().ListRestores(namespace, metav1.ListOptions{}); err != nil {
		msg := fmt.Errorf("failed to list restoreCR in namespace[%s]. err: %v", namespace, err)
		return nil, errors.NewInternalError(msg)
	}
	var startTime, endTime int64
	var page, itemsPerPage int

	if extraOption.StartTimeInSec != 0 {
		startTime = extraOption.StartTimeInSec
	} else {
		startTime = time.Now().Unix() - 24*3600
	}

	if extraOption.EndTimeInSec != 0 {
		endTime = extraOption.EndTimeInSec
	} else {
		endTime = time.Now().Unix()
	}

	if extraOption.ItemsPerPage != 0 {
		itemsPerPage = extraOption.ItemsPerPage
	} else {
		itemsPerPage = 10
	}

	if extraOption.Page != 0 {
		page = extraOption.Page
	} else {
		page = 1
	}

	itemTotal = int64(len(restoreCRs.Items))

	for _, restoreCR := range restoreCRs.Items {
		if startTime < restoreCR.CreationTimestamp.Unix() && restoreCR.CreationTimestamp.Unix() < endTime {
			restoreList = append(restoreList, *s.store.Controller().AssembleTiDBClusterRestore(&restoreCR))
		}
	}

	return &apiserver.TiDBClusterRestoreList{
		Items: utils.Pagination(page, itemsPerPage, restoreList).([]apiserver.TiDBClusterRestore),
		Total: itemTotal,
	}, nil
}

func (s *Storage) NewList() runtime.Object {
	return &apiserver.TiDBClusterRestoreList{}
}

func (s *Storage) NamespaceScoped() bool {
	return true
}

func (s *Storage) NewExtraListOptions() runtime.Object {
	return &apiserver.BackupListOptions{}
}
