package dataplane

import (
	"context"
	"fmt"
	"strconv"

	"helm.sh/helm/v3/pkg/action"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/aws-iam-authenticator/pkg/token"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	compatcst "github.com/tidbcloud/infra-provider/pkg/compat/consts"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	// Namespace for Helm to store data.
	HelmNamespace      = "helm"
	ErrNoDataPlaneName = "no dataplane name"
	ErrNotImp          = "not implemented"
)

var _ k8s.ClientGetter = &clientGetter{}

type clientGetter struct {
	cli           client.Client
	clientOptions client.Options

	// External information of the EKS cluster
	clusterName   string
	clusterRegion string

	// Created in the construction
	clientManager k8s.ClientManager
	tokenGen      token.Generator
}

// NetworkClientGetter return the getter to get k8s client.
func NetworkClientGetter(ctx context.Context, cli client.Client, network *cloudv1.Network, scheme *runtime.Scheme) (k8s.ClientGetter, error) {
	_, ok := network.Annotations[compatcst.CompatFlagAnnotation]
	if !ok {
		// If the network is not a compat network, use the original network client getter.
		return k8s.NetworkClientGetter(ctx, cli, network, scheme)
	}

	var clientOptions client.Options
	if scheme != nil {
		clientOptions = client.Options{
			Scheme: scheme,
		}
	}
	var err error
	var ready bool
	tokenGen, err := token.NewGenerator(false, false)
	if err != nil {
		return nil, err
	}
	name, ok := network.Annotations[consts.AnnotationKeyDataplaneName]
	if !ok {
		return nil, fmt.Errorf(ErrNoDataPlaneName)
	}
	g := &clientGetter{
		cli:           cli,
		clientOptions: clientOptions,
		clusterName:   name,
		clusterRegion: network.Spec.Region,
		tokenGen:      tokenGen,
	}

	switch network.Spec.Provider {
	case consts.ProviderTypeEKS:
		g.clientManager = NewEKSClientManager(*network, clientOptions)
		// We do not create connection for full manager network.
	case consts.ProviderTypeGKE:
		g.clientManager = NewGKEClientManager(*network, clientOptions)
	default:
		return nil, fmt.Errorf("unknown provider type")
	}
	// wait cluster ready
	ready, err = k8sReady(network)
	if err != nil {
		return nil, err
	}
	if !ready {
		return nil, util.WrapError("connection is not ready", err)
	}
	return g, nil
}

func (g *clientGetter) GetClient(ctx context.Context) (client.Client, error) {
	return g.clientManager.GetClient(ctx, types.NamespacedName{Name: g.clusterName})
}

func (g *clientGetter) GetHelmConfig(ctx context.Context) (*action.Configuration, error) {
	return nil, fmt.Errorf(ErrNotImp)
}

func k8sReady(network *cloudv1.Network) (ready bool, err error) {
	readystr, ok := network.Annotations[compatcst.CompatK8SReadyAnnotation]
	if !ok {
		return false, nil
	}
	isready, err := strconv.ParseBool(readystr)
	if err != nil {
		return false, err

	}
	if isready {
		return true, nil
	}
	return false, nil
}

func Apply(ctx context.Context, c client.Client, obj client.Object) error {
	if _, ok := obj.GetAnnotations()[compatcst.CompatFlagAnnotation]; !ok {
		return k8s.Apply(ctx, c, obj)
	}
	// in compat mode, we should use the compat client to apply the object
	// compat client will handle the idempotent logic
	return c.Create(ctx, obj)
}

func DeleteObject(ctx context.Context, cli client.Writer, obj client.Object) error {
	if err := client.IgnoreNotFound(cli.Delete(ctx, obj)); err != nil {
		return err
	}
	return nil
}

type Reader struct {
	Cli    client.Client
	Cache  client.Reader
	Scheme *runtime.Scheme
}

func (r *Reader) GetClient(ctx context.Context, c *cloudv1.Cluster) (client.Client, error) {
	if c == nil {
		return nil, fmt.Errorf("empty cluster")
	}
	var network cloudv1.Network
	key := c.GetNetworkRef()
	if err := r.Cache.Get(ctx, key, &network); err != nil {
		return nil, fmt.Errorf("unable to get network %s: %v", c.Spec.NetworkName, err)
	}

	g, err := NetworkClientGetter(ctx, r.Cli, &network, r.Scheme)
	if err != nil {
		return nil, err
	}
	return g.GetClient(ctx)
}

func (r *Reader) GetKubernetesStatus(c *cloudv1.Cluster) (*cloudv1.KubernetesStatus, error) {
	switch c.GetProviderType() {
	case cloudv1.ProviderTypeEKS:
		s, err := cloudv1.EKSClusterStatusFromCluster(c)
		if err != nil {
			return nil, err
		}
		return s.Kubernetes, nil
	case cloudv1.ProviderTypeGKE:
		s, err := cloudv1.GKEClusterStatusFromCluster(c)
		if err != nil {
			return nil, err
		}
		return s.Kubernetes, nil
	default:
		return nil, fmt.Errorf("unknown provider type %s", c.GetProviderType())
	}
}
