package jumper

import (
	"context"
	"fmt"
	"strings"

	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"
	ngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"

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

type PremiumResourcePoolJumper 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 NewPremiumResourcePoolJumper(dep JumperDependency) (Jumper, error) {
	j := &PremiumResourcePoolJumper{
		coresvcCli:        dep.CoreSvc,
		mgmtHTTP:          dep.MgmtHTTP,
		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 *PremiumResourcePoolJumper) PrepareJump(ctx context.Context, jp JumpPodForJumper, idest jpent.IDestination, param *types.PrepareJumpParam) (*types.JumpParam, error) {
	dest, ok := jpent.AsDestinationPremiumResourcePool(idest)
	if !ok {
		return nil, fmt.Errorf("invalid destination type %s", idest.Type())
	}

	pool, dataplane, err := j.queryResourcePoolAndDataplane(ctx, dest)
	if err != nil {
		return nil, err
	}

	var jmpParam *types.JumpParam
	err = j.DoWithCachedConfig(ctx, dest, pool, 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%d", dest.ResourcePoolID)),
		}
		jmpParam, err = k8sJumper.PrepareJump(ctx, jp, dest, param, opt)
		return err
	})

	return jmpParam, err
}

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

	pool, dataplane, err := j.queryResourcePoolAndDataplane(ctx, dest)
	if err != nil {
		return err
	}

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

	pool, dataplane, err := j.queryResourcePoolAndDataplane(ctx, dest)
	if err != nil {
		return nil, err
	}

	var jmpParam *types.JumpParam
	err = j.DoWithCachedConfig(ctx, dest, pool, 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("default"),
		}
		jmpParam, err = k8sJumper.RefreshJump(ctx, jp, dest, attr, param, opt)
		return err
	})

	return jmpParam, err
}

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

func (j *PremiumResourcePoolJumper) DoWithCachedConfig(ctx context.Context, dest *jpent.DestinationPremiumResourcePool, rp *ngv1.ResourcePoolProfile, dp *mgmthttpcli.DataPlane, do func(ctx context.Context, cfg *api.Config) error) error {
	key := dp.ID

	// load or store config
	_, exist := j.configs.Load(key)
	if !exist {
		config, err := j.buildConfig(dest, rp, dp)
		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 *PremiumResourcePoolJumper) queryResourcePoolAndDataplane(ctx context.Context, dest *jpent.DestinationPremiumResourcePool) (*ngv1.ResourcePoolProfile, *mgmthttpcli.DataPlane, error) {
	pool, err := j.coresvcCli.Mgmt.ResourcePool.GetResourcePool(ctx, &ngv1.GetResourcePoolRequest{
		ResourcePoolId: dest.ResourcePoolID,
	})
	if err != nil {
		return nil, nil, fmt.Errorf("get resource pool %d: %w", dest.ResourcePoolID, err)
	}
	if pool.ResourcePool == nil {
		return nil, nil, fmt.Errorf("resource pool %d return but is nil", dest.ResourcePoolID)
	}

	dataplane, err := j.mgmtHTTP.NextGenRegionServer(pool.Region.GetLogicalName(), strings.ToLower(pool.GetResourcePool().ProviderId.String())).
		GetNextGenDataplane(ctx, pool.ResourcePool.DataplaneId)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get dataplane %s from region server: %w", dest.ID(), err)
	}
	if dataplane.Kubernetes.Name == "" {
		return nil, nil, fmt.Errorf("dataplane %s has no Kubernetes", dataplane.ID)
	}

	return pool, dataplane, nil
}

func (j *PremiumResourcePoolJumper) buildConfig(dest *jpent.DestinationPremiumResourcePool, pool *ngv1.ResourcePoolProfile, dataplane *mgmthttpcli.DataPlane) (*api.Config, error) {
	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:        pool.ResourcePool.ProviderId,
		Region:            premiumAWSRegionFromResourcePool(pool),
		K8sName:           dataplane.Kubernetes.Name,
		Endpoint:          endpoint,
		RawConfig:         rawConfig,
		AKSAuth:           j.aksAuth,
		ACKAuth:           j.ackAuth,
		NewArchAccessRole: j.newArchAccessRole,
	})
}
