package task

import (
	"context"
	"fmt"
	"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/config"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/nodegroup"
)

const (
	Finalizer = "compactbackup.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

	compactBackup *cloudv1.CompactBackup
	cluster       *cloudv1.Cluster
	clusterNamer  *namer.Namer

	env    string
	cloud  CloudProvider
	config config.CloudConfigProvider

	beginAt *time.Time
}

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

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()
}

func (t *baseTask) MetricsStartTime() *time.Time {
	return t.beginAt
}

func (t *baseTask) getCompactBackupCRName() string {
	return t.compactBackup.Name
}

// getCompactName returns the prefix of compaction files in storage.
// for example, if the compact backup name is "cb-123", compaction files will be stored in "v1/compactions/cb-123/c_{HASH}_{SHARD optional}".
func (t *baseTask) getCompactName() string {
	return fmt.Sprintf("%s/c", t.compactBackup.Name)
}

// getNodePoolName returns a human-readable nodegroup name bounded to 63 chars.
// namer.NewNameHashOnlyExceed panics if the remaining identity budget
// (limit − len(prefix) − 2 − len(suffix)) is ≤ hashSize (8). All known
// instance type names across clouds are well within this limit:
//   - AWS: e.g., trn1n.32xlarge → 14 chars (dots replaced with hyphens)
//   - GCP: e.g., n2-standard-96 → 14 chars (no dots or underscores)
//   - Azure: e.g., Standard_E104ids_v5 → 19 chars (underscores replaced with hyphens)
func (t *baseTask) getNodePoolName(instanceType, k8sName, k8sVersion string) string {
	normalizedType := strings.ReplaceAll(instanceType, ".", "-")
	normalizedType = strings.ReplaceAll(normalizedType, "_", "-")
	return namer.NewNameHashOnlyExceed(k8sName, "compact-"+normalizedType, nodegroup.NormalizeVersion(k8sVersion), 63)
}

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

func (t *baseTask) getProjectID() string {
	return t.compactBackup.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
// For AWS, providerconfig reuse the cluster providerconfig, and recycle by the cluster provierconfig task
// For GCP/Azure, providerconfig is reuse the cluster providerconfig too, but is dataplane-level.
type ProviderConfiger interface {
	GetProviderConfigName() string
}

// NodePooler builds a vendor specific node pool CR
// For AWS, the nodepool is created in cluster-level, use cluster-level providercofnig, noderole, nodegroup, pod affinity
// For GCP/Azure, the nodepool is created in dataplane-level, use dataplane-level providerconfig, noderole, nodegroup, pod affinity
// nodepool labels and pod affinity is used for node selector
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 {
	OverlayCompact(*opv1alpha1.CompactBackup) error
	OverlayCompactStatus(*cloudv1.CompactBackup, *opv1alpha1.CompactBackup) error
}
