package jumper

import (
	"context"
	"fmt"
	"strconv"

	jpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	k8sutil "github.com/tidbcloud/devops/pkg/infra/utils/k8s"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"

	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/clientcmd/api"
)

type PremiumClusterJumper struct {
	coresvcCli       *coresvccli.CoreSvcCli
	mgmtHTTP         mgmthttpcli.MgmtHTTPClient
	k8sJumperFactory *K8sJumperFactory

	newArchAccessRole string
	aksAuth           *k8sutil.AKSAuth
	ackAuth           *config.JumpServiceDestinationAliCloudAuth
	configs           *container.Map[string, *api.Config] // TODO: clean cache
}

func NewPremiumClusterJumper(dep JumperDependency) (Jumper, error) {
	j := &PremiumClusterJumper{
		mgmtHTTP:          dep.MgmtHTTP,
		coresvcCli:        dep.CoreSvc,
		k8sJumperFactory:  dep.K8sJumperFactory,
		newArchAccessRole: dep.Conf.Dedicated.DedicatedNewArchAccessRole,
		aksAuth:           newPremiumAKSAuth(dep.Conf),
		ackAuth:           dep.Conf.Premium.PremiumACKAuth,
		configs:           container.NewMap[string, *api.Config](),
	}
	return j, nil
}

func (j *PremiumClusterJumper) PrepareJump(ctx context.Context, jp JumpPodForJumper, idest jpent.IDestination, param *types.PrepareJumpParam) (*types.JumpParam, error) {
	dest, ok := jpent.AsDestinationPremiumCluster(idest)
	if !ok {
		return nil, fmt.Errorf("invalid destination type %s", idest.Type())
	}

	cluster, dataplane, err := j.queryDataplane(ctx, dest.ClusterID)
	if err != nil {
		return nil, err
	}
	if dataplane.Kubernetes.Name == "" {
		return nil, fmt.Errorf("dataplane %s for cluster %d has no Kubernetes", dataplane.ID, dest.ClusterID)
	}

	var jmpParam *types.JumpParam
	err = j.DoWithCachedConfig(ctx, cluster, dataplane, func(ctx context.Context, config *api.Config) error {
		// proxy to k8s jumper
		k8sJumper, err := j.k8sJumperFactory.NewK8sTokenJumper(*config)
		if err != nil {
			return fmt.Errorf("new k8s jumper: %w", err)
		}

		opt := K8sJumpOption{
			DefaultNamespace: pointer.Pointer(fmt.Sprintf("tidb%s", cluster.ClusterInfo.(*mgmtv1.Cluster_NextGenCluster).NextGenCluster.SharedPoolId)),
		}
		jmpParam, err = k8sJumper.PrepareJump(ctx, jp, dest, param, opt)
		return err
	})

	return jmpParam, err
}

func (j *PremiumClusterJumper) FinalizeJump(ctx context.Context, jp JumpPodForJumper, idest jpent.IDestination, attr jpent.JumpAttributes) error {
	dest, ok := jpent.AsDestinationPremiumCluster(idest)
	if !ok {
		return fmt.Errorf("invalid destination type %s", idest.Type())
	}

	cluster, dataplane, err := j.queryDataplane(ctx, dest.ClusterID)
	if err != nil {
		return err
	}
	if dataplane.Kubernetes.Name == "" {
		return fmt.Errorf("dataplane %s for cluster %d has no Kubernetes", dataplane.ID, dest.ClusterID)
	}

	return j.DoWithCachedConfig(ctx, cluster, dataplane, func(ctx context.Context, config *api.Config) error {
		// proxy to k8s jumper
		k8sJumper, err := j.k8sJumperFactory.NewK8sTokenJumper(*config)
		if err != nil {
			return fmt.Errorf("new k8s jumper: %w", err)
		}

		return k8sJumper.FinalizeJump(ctx, jp, dest)
	})
}

func (j *PremiumClusterJumper) RefreshJump(ctx context.Context, jp JumpPodForJumper, idest jpent.IDestination, attr jpent.JumpAttributes, param *types.RefreshJumpParam) (*types.JumpParam, error) {
	dest, ok := jpent.AsDestinationPremiumCluster(idest)
	if !ok {
		return nil, fmt.Errorf("invalid destination type %s", idest.Type())
	}

	cluster, dataplane, err := j.queryDataplane(ctx, dest.ClusterID)
	if err != nil {
		return nil, err
	}
	if dataplane.Kubernetes.Name == "" {
		return nil, fmt.Errorf("dataplane %s for cluster %d has no Kubernetes", dataplane.ID, dest.ClusterID)
	}

	var jmpParam *types.JumpParam
	err = j.DoWithCachedConfig(ctx, cluster, dataplane, func(ctx context.Context, config *api.Config) error {
		// proxy to k8s jumper
		k8sJumper, err := j.k8sJumperFactory.NewK8sTokenJumper(*config)
		if err != nil {
			return fmt.Errorf("new k8s jumper: %w", err)
		}

		opt := K8sJumpOption{
			DefaultNamespace: pointer.Pointer(fmt.Sprintf("tidb%s", cluster.ClusterInfo.(*mgmtv1.Cluster_NextGenCluster).NextGenCluster.SharedPoolId)),
		}
		jmpParam, err = k8sJumper.RefreshJump(ctx, jp, dest, attr, param, opt)
		return err
	})

	return jmpParam, err
}

func (j *PremiumClusterJumper) SupportedDestinations() (jpent.DestinationType, []string) {
	typ := jpent.DestinationTypePremium
	return typ, nil
}

func (j *PremiumClusterJumper) DoWithCachedConfig(ctx context.Context, cluster *mgmtv1.Cluster, dataplane *mgmthttpcli.DataPlane, do func(ctx context.Context, cfg *api.Config) error) error {
	key := dataplane.ID

	// load or store config
	_, exist := j.configs.Load(key)
	if !exist {
		config, err := j.buildConfig(cluster, dataplane)
		if err != nil {
			return err
		}
		j.configs.Store(key, config)
	}

	// do
	config, _ := j.configs.Load(key)
	err := do(ctx, config)
	if err != nil {
		j.configs.Delete(key) // clear invalid config
		return err
	}

	return nil
}

func (j *PremiumClusterJumper) queryDataplane(ctx context.Context, clusterID uint64) (*mgmtv1.Cluster, *mgmthttpcli.DataPlane, error) {
	// Get cluster info
	cluster, err := j.coresvcCli.Mgmt.GetCluster(ctx, clusterID)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get cluster %d: %w", clusterID, err)
	}
	ngClusterInfo, ok := cluster.ClusterInfo.(*mgmtv1.Cluster_NextGenCluster)
	if !ok || ngClusterInfo.NextGenCluster == nil {
		return nil, nil, fmt.Errorf("cluster %d is not a next-gen cluster", clusterID)
	}
	ngCluster := ngClusterInfo.NextGenCluster
	if ngCluster.DataplaneId == "" {
		return nil, nil, fmt.Errorf("dataplane id of cluster %d is empty", clusterID)
	}

	dataplane, err := j.mgmtHTTP.NextGen().GetDataPlane(ctx, strconv.FormatUint(clusterID, 10), ngCluster.DataplaneId)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get dataplane for cluster %d: %w", clusterID, err)
	}

	return cluster, dataplane, nil
}

func (j *PremiumClusterJumper) buildConfig(cluster *mgmtv1.Cluster, dataplane *mgmthttpcli.DataPlane) (*api.Config, error) {
	ngClusterInfo, ok := cluster.ClusterInfo.(*mgmtv1.Cluster_NextGenCluster)
	if !ok || ngClusterInfo.NextGenCluster == nil {
		return nil, fmt.Errorf("cluster is not a next-gen cluster")
	}
	ngCluster := ngClusterInfo.NextGenCluster

	rawConfig, err := clientcmd.Load(dataplane.Kubernetes.Kubeconfig)
	if err != nil {
		return nil, fmt.Errorf("failed to load kubeconfig: %w", err)
	}

	endpoint := ""
	for _, cluster := range rawConfig.Clusters {
		endpoint = cluster.Server // We assume only one cluster is defined in the kubeconfig
		break
	}

	return buildPremiumDataplaneConfig(premiumDataplaneConfigInput{
		ProviderID:        ngCluster.CloudProviderId,
		Region:            cluster.Region,
		K8sName:           dataplane.Kubernetes.Name,
		Endpoint:          endpoint,
		RawConfig:         rawConfig,
		AKSAuth:           j.aksAuth,
		ACKAuth:           j.ackAuth,
		NewArchAccessRole: j.newArchAccessRole,
	})
}
