package cloud

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

	cdcv1 "github.com/tidbcloud/ticdc-service/api/v1"
	"github.com/tidbcloud/ticdc-service/pkg/consts"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	// Following underscore imports are placeholder import to
	// pin upbound provider and crossplane-runtime dependency
	// If we imports these packages in other files, then following
	// imports can be removed

	// upbound gcp-provider
	_ "github.com/upbound/provider-gcp/apis/compute/v1beta1"
	_ "github.com/upbound/provider-gcp/apis/container/v1beta1"
	_ "github.com/upbound/provider-gcp/apis/dns/v1beta1"
	_ "github.com/upbound/provider-gcp/apis/v1beta1"

	// upbound azure-provider
	_ "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	_ "github.com/upbound/provider-azure/apis/network/v1beta1"
	_ "github.com/upbound/provider-azure/apis/v1beta1"

	// upbound aws-provider
	_ "github.com/upbound/provider-aws/apis/ec2/v1beta1"
	_ "github.com/upbound/provider-aws/apis/eks/v1beta1"
	_ "github.com/upbound/provider-aws/apis/v1beta1"
)

const (
	defaultRootDiskSizeGB = 50
	maxNodeCount          = 100
)

type Provider interface {
	NodePoolProvider
	StorageProvider
	ProviderConfiger
	K8sGetter
	EndpointProvider
	DNSProvider
}

type NodePoolProvider interface {
	BuildNodePool(string, types.NamespacedName, *NodePool) (client.Object, error)
	PostCreateNodePool(ctx context.Context, cli client.Client, resourceTags map[string]string) error
	DeleteNodePool(context.Context, client.Client, types.NamespacedName) error
}

type StorageProvider interface {
	BuildStorageClass(types.NamespacedName, *StorageClass) (client.Object, error)
	DeleteStorageClass(context.Context, client.Client, types.NamespacedName) error
}

type CertificateProvider interface {
	BuildCertificate(types.NamespacedName, *CertificateRequest) (client.Object, error)
	DeleteCertificate(context.Context, client.Client, types.NamespacedName) error
}

// EndpointProvider builds a cloud vendor specific endpoint CR
// It's also responsible for deleting the endpoint CR and getting the endpoint detail
type EndpointProvider interface {
	BuildSinkEndpoint(string, types.NamespacedName, *EndpointRequest) ([]client.Object, error)
	DeleteSinkEndpoint(context.Context, client.Client, types.NamespacedName) error
	GetSinkEndpointDetail(context.Context, client.Client, types.NamespacedName) (*SinkEndpoint, error)
	ValidateEndpointZones(ctx context.Context, endpointZones []string, serviceName string) error
}

// DNSProvider builds a cloud vendor specific DNS CR
type DNSProvider interface {
	BuildDNS(providerConfig string, dns *DNSRequest) ([]client.Object, error)
	DeleteDNS(context.Context, client.Client, string) error
}

// GCProvider combine the GCExecutor and AppboxLister
type GCProvider interface {
	GCExecutor
	AppboxLister
}

// GCExecutor is responsible for deleting the appbox
type GCExecutor interface {
	// OnAppboxDeleting is called when a appbox is in deleting status
	OnAppboxDeleting(context.Context, string) error
}

// AppboxLister is responsible for listing the appbox that used by the ticdc service
type AppboxLister interface {
	ListAppbox(context.Context) ([]string, error)
}

type ResourceTagCompensater interface {
	CompensateComputeTags(ctx context.Context, cli client.Client) error
}

type CertificateRequest struct {
	Duration    *metav1.Duration
	RenewBefore *metav1.Duration
	DNSNames    []string
	IPAddresses []string
}

// 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 {
	BuildProviderConfig(types.NamespacedName) (client.Object, error)
	DeleteProviderConfig(context.Context, client.Client, types.NamespacedName) error
}

// K8sGetter gets the k8s cluster info
type K8sGetter interface {
	GetKubeClient(context.Context) (client.Client, error)
	GetKubernetesName(context.Context) (string, error)
	GetKubernetesVersion(context.Context) (string, error)
	GetKubernetesID(context.Context) (string, error)
	GetNodeZones(context.Context) ([]string, error)
	GetNodeSubnets(context.Context) ([]string, error)
	GetPodSubnets(context.Context) ([]string, error)
	GetSecondaryNodeSubnets(context.Context) ([]string, error)
}

type NodePool struct {
	Name           string // this may not be the same as the corresponding CR
	Arch           string
	Region         string
	Zones          []string
	NodeCount      int32 // if omit, auto scaling will be enabled
	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
}

type StorageClass struct {
	Type          string
	Iops          uint32
	ThroughputMbs uint32
}

type SinkEndpoint struct {
	EndpointID  string
	Phase       cdcv1.SinkEndpointPhase
	Address     string
	ZoneAddress map[string]string
}

type EndpointRequest struct {
	Region              string
	CloudEndpointName   string
	EndpointServiceName string
	// availability zones for the endpoint, aws only
	SelectedZones []string

	// key-value pairs attached to cloud resources
	Tags map[string]string
}

type DNSRequest struct {
	Name   string
	Region string

	RootDomainName string
	Endpoint       *SinkEndpoint
	// aws only
	ZoneLevelDNS map[string]string

	// key-value pairs attached to cloud resources
	Tags map[string]string
}

// must be no more than 63 characters
func shortName(name string) string {
	hash := md5.Sum([]byte(name))
	hashString := hex.EncodeToString(hash[:])
	// prefix with `n` to avoid name starts with a number
	return fmt.Sprintf("n%s", hashString[:11])
}

func appendAppboxIDLabel(labels map[string]string, appboxID string) map[string]string {
	if labels == nil {
		labels = map[string]string{}
	}
	labels[consts.AppboxIDLabel] = appboxID
	return labels
}
