package task

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/namer"

	"github.com/go-logr/logr"
	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"
)

const (
	Finalizer                 = "replay.cloud.pingcap.com/finalizer"
	AnnoKeyPrefixUsedByReplay = "alpha.usedby.replay.cloud.pingcap.com/"
	AnnoValInUse              = "in-use"
	ReplayComponent           = "replay"
	NodeSelectorKey           = "replay-name"
	NodeInstanceTypeKey       = "replay-instance-type"
	DefaultReplayNodeSpec     = "32C64G"
	DefaultInstanceType       = "c6g.8xlarge"
	DefaultSvcPort            = 8080
	ReplayStorageClass        = "replay-storage-class"
	DefaultDiskSize           = "2000Gi"

	// LaunchTemplateUserData configures kernel parameters for Bottlerocket
	// Bottlerocket uses TOML format for user data
	// Reference: https://github.com/bottlerocket-os/bottlerocket#using-user-data
	LaunchTemplateUserData = `[settings.kernel.sysctl]
"net.ipv4.tcp_tw_reuse" = "1"
"net.ipv4.ip_local_port_range" = "1024 65535"`
)

type ReplayDeploymentProvider interface {
	Tasks() []deploy.Task

	ReplayNodeAffinity(replayTask cloudv1.ReplayTask) *corev1.Affinity
	ReplayTolerations() []corev1.Toleration
	ReplayZones() []string
	ReplaySubnets() []string
}

type Context struct {
	Recorder   record.EventRecorder
	Logger     logr.Logger
	Local      client.Client // controlplane k8s client
	Remote     client.Client // dataplane k8s client
	Replay     *cloudv1.Replay
	beginAt    *time.Time
	RootConfig config.RootConfig
	AWSAccount string
	Zones      []string
	Subnets    []string
	NodeLabels map[string]string

	Provider ReplayDeploymentProvider
}

func NewContext(
	recorder record.EventRecorder,
	logger logr.Logger,
	local client.Client,
	remote client.Client,
	replay *cloudv1.Replay,
	rootConfig config.RootConfig,
) *Context {
	return &Context{
		Recorder:   recorder,
		Logger:     logger,
		Local:      local,
		Remote:     remote,
		Replay:     replay,
		RootConfig: rootConfig,
		AWSAccount: replay.Spec.Kubernetes.Account, // Get from Replay CR
	}
}

func (c *Context) GetRegion() string {
	return c.RootConfig.Network.Region
}

func (c *Context) GetClusterName() string {
	return c.Replay.Spec.TargetClusterID
}

func (c *Context) GetClusterNameSpace() string {
	return cloudv1.ClusterPrefixId(c.GetClusterName())
}

func (c *Context) GetClusterScopeName() string {
	return fmt.Sprintf("%s-%s", c.GetClusterNameSpace(), c.GetClusterName())
}

func (c *Context) GetReplayNameSpace() string {
	return fmt.Sprintf("replay-%s", c.Replay.Name)
}

// GetEKSProviderConfigName returns the name of the eks Crossplane ProviderConfig object.
func (c *Context) GetEKSProviderConfigName() string {
	// pc-{tidb cluster name}-{hash}-replay
	return namer.NewName(c.Replay.Spec.Kubernetes.Name, "pc", "replay", -1)
}

type BaseTask struct {
	*Context
}

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

func (t *BaseTask) Category() deploy.TaskCategory {
	return deploy.ReplayTask
}

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
}

// GenerateStatefulSetName generates a StatefulSet name with length validation
// Keeps names under 63 chars by using hash when necessary
// This function is used by both task.common and controller packages
func GenerateStatefulSetName(replayName, taskName string) string {
	const maxPodNameLength = 63
	const ordinalSuffix = 3 // "-{ordinal}" supports up to 2-digit ordinal (e.g., "-99")
	const hashLength = 10   // hash length is 32, we only use 10 chars
	var maxStsNameLength = maxPodNameLength - ordinalSuffix
	combined := fmt.Sprintf("%s-%s", replayName, taskName)
	if len(combined) <= maxStsNameLength {
		return combined
	}
	hash := generateHash(combined)
	maxStsNameLength = maxStsNameLength - hashLength - 1 // leave space for "-"
	return fmt.Sprintf("%s-%s", combined[:maxStsNameLength], hash[:hashLength])
}

func GeneratePodName(replayName, taskName string, index int32) string {
	baseName := GenerateStatefulSetName(replayName, taskName)
	return fmt.Sprintf("%s-%d", baseName, index)
}

func generateHash(s string) string {
	hash := md5.Sum([]byte(s))
	return hex.EncodeToString(hash[:])
}
