package task

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/go-logr/logr"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

const (
	Finalizer = "restore.cloud.pingcap.com/finalizer"
)

type Context struct {
	recorder record.EventRecorder
	logger   logr.Logger

	local  client.Client // controlplane k8s client
	remote client.Client // dataplane k8s client

	restore      *cloudv1.Restore
	cluster      *cloudv1.Cluster
	clusterNamer *clusternamer.Namer

	env   string
	cloud CloudProvider

	beginAt *time.Time
}

func NewContext(recorder record.EventRecorder, logger logr.Logger, local client.Client, remote client.Client, restore *cloudv1.Restore, cluster *cloudv1.Cluster, env string, cloud CloudProvider) *Context {
	clusterName := restore.Spec.ClusterName
	tidbNs := cloudv1.ClusterPrefixId(clusterName)
	clusterNamer := clusternamer.NewNamer().WithClusterName(clusterName).WithTiDBNamespace(tidbNs)
	return &Context{
		recorder:     recorder,
		logger:       logger,
		local:        local,
		remote:       remote,
		restore:      restore,
		cluster:      cluster,
		clusterNamer: clusterNamer,
		env:          env,
		cloud:        cloud,
	}
}

type baseTask struct {
	*Context
}

func (t *baseTask) Name() string {
	return "Base"
}

func (t *baseTask) Category() deploy.TaskCategory {
	return deploy.BRTask
}

func (t *baseTask) Create(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *baseTask) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *baseTask) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *baseTask) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// MetricsStartTime will return the `BeginAt` (nil by default). For
// Task whose metrics need to be exported, the `BeginAt` should be
// initialized when the `Create` is being called.
func (t *baseTask) MetricsStartTime() *time.Time {
	return t.beginAt
}

func (t *baseTask) getRestoreName() string {
	return t.restore.Name
}

func (t *baseTask) getNodePoolName(instanceType, k8sName, k8sVersion string, zones []string) string {
	// sort zones to make the nodepool name consistent
	zs := make([]string, len(zones))
	copy(zs, zones)
	sort.Strings(zs)
	return fmt.Sprintf("np-restore-%s-%s-%s-%s", instanceType, k8sName, k8sVersion, strings.Join(zs, "-"))
}

func (t *baseTask) getTenantID() string {
	return t.restore.Labels[cloudv1.TenantTagKey]
}

func (t *baseTask) getProjectID() string {
	return t.restore.Labels[cloudv1.ProjectTagKey]
}

type CloudProvider interface {
	ProviderConfiger
	NodePooler
	K8sGetter
	StorageOverlayer
}

// ProviderConfiger builds a cloud vendor specific config CR
// This config will be used to interact with the cloud
// When using crossplane, the config is the crossplane ProviderConfig CR
// If not using crossplane, the config might be a ConfigMap or a Secret
type ProviderConfiger interface {
	GetProviderConfigName() string
}

// NodePooler builds a vendor specific node pool CR
type NodePooler interface {
	BuildNodePool(ctx context.Context, pc string, np *NodePool) ([]client.Object, error)
	GetNodepoolLabels() map[string]string
	GetPodAffinity() *corev1.Affinity
}

type NodePool struct {
	Name           string // this may not be the same as the corresponding CR
	Arch           string
	Region         string
	Zones          []string
	InstanceType   string
	RootDiskSizeGB int64
	Identity       string
	KubeClusterID  string
	NodeSubnets    []string
	PodSubnets     []string
	Taints         []corev1.Taint
	// Labels are k8s node labels
	Labels map[string]string
	// key-value pairs attached to cloud resources
	Tags map[string]string
}

// K8sGetter gets the k8s cluster info
type K8sGetter interface {
	GetKubernetesName() string
	GetKubernetesID() string
	GetKubernetesVersion() string
	GetNodeIdentity() string
	GetNodeZones() []string
	GetNodeSubnets() []string
	GetPodSubnets() []string
}

type StorageOverlayer interface {
	Overlay(cloudRestore *cloudv1.Restore, opRestore *opv1alpha1.Restore) error
}
