package jumper

import (
	"context"
	"fmt"

	"k8s.io/client-go/tools/clientcmd/api"

	jpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/clients/dedicatedcli"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	k8sutil "github.com/tidbcloud/devops/pkg/infra/utils/k8s"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"
)

// ControlPlaneJumper knows how to jump to a control plane cluster.
type ControlPlaneJumper struct {
	infraCli         dedicatedcli.InfraAPIClient
	registry         *DestinationRegistry
	k8sJumperFactory *K8sJumperFactory

	configs *container.Map[string, *api.Config]
}

func NewControlPlaneJumper(dep JumperDependency) (Jumper, error) {
	j := &ControlPlaneJumper{
		infraCli:         dep.Infra,
		registry:         dep.DestinationRegistry,
		k8sJumperFactory: dep.K8sJumperFactory,

		configs: container.NewMap[string, *api.Config](),
	}
	return j, nil
}

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

	// load or create cluster config
	config, err := j.loadClusterConfig(dest.Name)
	if err != nil {
		return nil, fmt.Errorf("load config: %w", err)
	}

	// proxy to k8s jumper
	k8sJumper, err := j.k8sJumperFactory.NewK8sTokenJumper(*config)
	if err != nil {
		return nil, fmt.Errorf("new k8s jumper: %w", err)
	}
	return k8sJumper.PrepareJump(ctx, jp, dest, param, K8sJumpOption{})
}

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

	// load or create cluster config
	config, err := j.loadClusterConfig(dest.Name)
	if err != nil {
		return fmt.Errorf("load config: %w", err)
	}

	// 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 *ControlPlaneJumper) RefreshJump(ctx context.Context, jp JumpPodForJumper, idest jpent.IDestination, attr jpent.JumpAttributes, param *types.RefreshJumpParam) (*types.JumpParam, error) {
	dest, ok := jpent.AsDestinationControlPlane(idest)
	if !ok {
		return nil, fmt.Errorf("invalid destination type %s", idest.Type())
	}

	// load or create cluster config
	config, err := j.loadClusterConfig(dest.Name)
	if err != nil {
		return nil, fmt.Errorf("load config: %w", err)
	}

	// proxy to k8s jumper
	k8sJumper, err := j.k8sJumperFactory.NewK8sTokenJumper(*config)
	if err != nil {
		return nil, fmt.Errorf("new k8s jumper: %w", err)
	}
	return k8sJumper.RefreshJump(ctx, jp, dest, attr, param, K8sJumpOption{})
}

func (j *ControlPlaneJumper) SupportedDestinations() (jpent.DestinationType, []string) {
	typ := jpent.DestinationTypeControlPlane

	names := []string{
		string(jpent.ControlPlaneInfraAPIServer),
	}
	names = append(names, j.registry.RegisteredDestinations(typ)...)

	return typ, names
}

func (j *ControlPlaneJumper) loadClusterConfig(cluster string) (*api.Config, error) {
	key := cluster

	config, exist := j.configs.Load(key)
	if exist {
		return config, nil
	}

	var err error
	switch cluster {
	case jpent.ControlPlaneInfraAPIServer:
		restConfig := j.infraCli.GetRestConfig()
		config = k8sutil.NewConfigBuilder().
			AddClusterFromRest(cluster, restConfig).
			AddAuthFromRest(cluster, restConfig).
			AddContext(cluster, api.Context{Cluster: cluster, AuthInfo: cluster}).
			CurrentContext(cluster).
			Build()
	default:
		config, err = j.registry.LoadConfig(jpent.DestinationTypeControlPlane, cluster)
	}
	if err != nil {
		return nil, fmt.Errorf("build config for %s failed: %w", cluster, err)
	}

	j.configs.Store(key, config)
	return config, nil
}
