package task

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

	"github.com/go-logr/logr"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	cdcv1 "github.com/tidbcloud/ticdc-service/api/v1"
	"github.com/tidbcloud/ticdc-service/pkg/cdccluster"
	"github.com/tidbcloud/ticdc-service/pkg/cloud"
	"github.com/tidbcloud/ticdc-service/pkg/config"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

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

type Context struct {
	Recorder record.EventRecorder
	Logger   logr.Logger
	Env      string // used to tag cloud resources

	Local client.Client // controlplane k8s client
	Cdc   *cdcv1.TiCDC  // controlplane cdc CR

	Remote    client.Client // dataplane k8s client
	Namespace string        // dataplane namespace

	CloudProvider   cloud.Provider
	ClusterProvider cdccluster.Provider
	CertProvider    cloud.CertificateProvider
	Scaler          cloud.Scaler

	Config      *config.Config
	ClusterRole string

	beginAt *time.Time
}

type baseTask struct {
	*Context
}

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

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

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
}

// ProviderConfig is shared by all cdc clusters in the same dataplane k8s cluster
// So we use k8sName to distinguish different dataplanes
func (t *baseTask) getProviderConfigName(k8sName string) string {
	return fmt.Sprintf("cdc-%s", k8sName)
}

// nodepool is shared by all cdc clusters in the same dataplane k8s cluster
func (t *baseTask) getNodePoolName(
	instanceType, k8sName, k8sVersion string, zones []string, secondarySubnets []string,
) string {
	// sort zones to make the nodepool name consistent
	zs := make([]string, len(zones))
	copy(zs, zones)
	sort.Strings(zs)
	if len(secondarySubnets) > 0 {
		return fmt.Sprintf(
			"np-cdc-%s-%s-%s-%s-with-secondary-%d",
			instanceType, k8sName, k8sVersion, strings.Join(zs, "-"), len(secondarySubnets))
	} else {
		return fmt.Sprintf("np-cdc-%s-%s-%s-%s", instanceType, k8sName, k8sVersion, strings.Join(zs, "-"))
	}
}

func (t *baseTask) getTaintValue(secondarySubnets []string) string {
	if len(secondarySubnets) > 0 {
		return fmt.Sprintf("tidb-%s-with-secondary-%d", string(cloudv1.ComponentTiCDC), len(secondarySubnets))
	} else {
		return fmt.Sprintf("tidb-%s", string(cloudv1.ComponentTiCDC))
	}
}

// name is hardcoded in tidb-operator v1, we must follow it
func (t *baseTask) getClusterCertName() string {
	component := cloudv1.ComponentTiCDC
	return fmt.Sprintf("%s-%s-cluster-secret", t.Cdc.Name, component)
}

// name is hardcoded in tidb-operator v1, we must follow it
func (t *baseTask) getClientCertName() string {
	component := "cluster"
	return fmt.Sprintf("%s-%s-client-secret", t.Cdc.Name, component)
}

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

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

func (t *baseTask) getStorageClassName() string {
	scType := t.Cdc.Spec.Storage.Type
	scIops := t.Cdc.Spec.Storage.Iops
	scThroughput := t.Cdc.Spec.Storage.ThroughputMbs
	return fmt.Sprintf("cdc-%s-%d-%d", scType, scIops, scThroughput)
}
