// Package task add by blacknc <blacknc@163.com> at 2024/08/28 00:20:44
package task

import (
	"fmt"
	"time"

	"github.com/go-logr/logr"
	"github.com/pingcap/errors"
	"go.opentelemetry.io/otel"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"

	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/dataplane"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

const (
	Finalizer              = "dm.cloud.pingcap.com/finalizer"
	ComponentName          = "dm"
	TiDBServerCertMountDir = "/var/lib/tidb-server-tls"
	ClusterCertMountDir    = "/var/lib/lightning-tls"

	AnnoKeyPrefixUsedByDM = "alpha.usedby.dmjob.cloud.pingcap.com/"
	AnnoValInUse          = "in-use"

	CloudLabelPrefix = "cloud.pingcap.com/"
)

var (
	Tracer = otel.Tracer("DMJobReconciler")
)

type Context struct {
	EnvProvider dataplane.EnvProvider
	Namer       *namer.Namer

	// Injected from the caller
	Local         client.Client
	EventRecorder record.EventRecorder

	Network *cloudv1.Network

	TenantID  string
	ProjectID string
	ClusterID string

	// DON'T update cluster in tasks actually
	// DON'T get or set cluster spec in tasks
	// Both will be done after all tasks are finished or aborted.
	// See update.go for more details.
	Cluster *regionalpb.Cluster
	// DON'T MODIFY ClusterRole in tasks
	ClusterRole  *regionalpb.ClusterRole
	Kubernetes   *cloudv1.Kubernetes
	DMJob        *v1alpha1.DMJob
	DMMasterSpec *v1alpha1.DMSpec
	DMWorkerSpec *v1alpha1.DMSpec

	Config config.RootConfig

	Logger logr.Logger

	// BeginAt denotes the starting time of the task.
	BeginAt *time.Time

	zones   []string
	subnets []string

	MasterNodePoolReady bool
	WorkerNodePoolReady bool

	MasterImage string
	WorkerImage string

	GCP gcp.Client
}

func (c *Context) Default() (*Context, error) {
	c.Namer = namer.NewNamer().
		WithClusterName(c.GetClusterName()).
		WithTiDBNamespace(c.GetClusterNameSpace())

	providerName := c.Config.Provider
	switch providerName {
	case consts.ProviderTypeEKS:
		// 3-layer architecture must get zones from cluster spec
		// ignoring the zone info in config file
		if cloudaws := c.Kubernetes.Cloud.AWS; cloudaws != nil {
			zones, subnets := cloudaws.SubnetGroups.SubnetsFilterByGroupNameOrZones([]cloudv1.AWSSubnetGroupName{
				cloudv1.AWSSubnetGroupNodes, cloudv1.AWSSubnetGroupSecondaryCIDR,
			}, []string{})

			c.zones = zones
			c.subnets = subnets
		}

	case consts.ProviderTypeGKE:
		if cloudgcp := c.Kubernetes.Cloud.GCP; cloudgcp != nil {
			c.zones = cloudgcp.NodeZones
		}

	case consts.ProviderTypeAKS:
		if cloudaks := c.Kubernetes.Cloud.Azure; cloudaks != nil {
			c.zones = cloudaks.NodeZones
		}

	default:
		return nil, fmt.Errorf("unknown provider type: %s", providerName)
	}

	if c.IsDataplaneExist() {
		if len(c.zones) == 0 {
			return nil, fmt.Errorf("available zones %v, the length of zones can not be 0", c.zones)
		}
	}

	if c.EnvProvider == nil {
		return nil, errors.New("EnvProvider is required")
	}

	c.MasterImage = fmt.Sprintf("%s:%s", c.Config.DMConfig.Master.Repo, c.Config.DMConfig.Master.Tag)
	if c.DMJob.Spec.Master.BaseImage != "" {
		c.MasterImage = c.DMJob.Spec.Master.BaseImage
	}
	c.WorkerImage = fmt.Sprintf("%s:%s", c.Config.DMConfig.Worker.Repo, c.Config.DMConfig.Worker.Tag)
	if c.DMJob.Spec.Worker.BaseImage != "" {
		c.WorkerImage = c.DMJob.Spec.Worker.BaseImage
	}

	return c, nil
}

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

func (c *Context) IsDataplaneExist() bool {
	return c.EnvProvider.GetClient() != nil
}

func (c *Context) IsClusterExist() bool {
	return c.Cluster != nil
}

func (c *Context) GetClusterName() string {
	return c.ClusterID
}

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

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

// GetProviderConfigName returns the name of the k8s Crossplane ProviderConfig object.
func (c *Context) GetProviderConfigName() string {
	// pc-{tidb cluster name}-{hash}-dmjob
	// all dmjob resources in the same tidb cluster share the same provider config
	return namer.NewName(c.GetClusterScopeName(), "pc", "dmjob", -1)
}

func (c *Context) Zones() []string {
	if c.zones == nil {
		panic("zone unreachable, may be forget init?")
	}
	return c.zones
}

// Subnets returns the subnet IDs for EKS node groups
func (c *Context) Subnets() []string {
	if c.subnets == nil {
		panic("subnets unreachable, may be forget init?")
	}
	return c.subnets
}

type KMSGetter interface {
	Get() string
}

type PCAGetter interface {
	Get() string
}

type GetFunc func() string

func (f GetFunc) Get() string {
	return f()
}
