package jumper

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"slices"
	"strings"

	jpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/config"
	secretsource "github.com/tidbcloud/devops/pkg/infra/secretsource"
	k8sutil "github.com/tidbcloud/devops/pkg/infra/utils/k8s"

	"gopkg.in/yaml.v3"
	"k8s.io/client-go/tools/clientcmd/api"
)

type DestinationRegistry struct {
	secretsource secretsource.SecretSource
	dests        map[jpent.DestinationType]map[string]config.JumpServiceDestination
}

func NewDestinationRegistryFromFile(filepath string, secretsource secretsource.SecretSource) (*DestinationRegistry, error) {
	m := make(map[jpent.DestinationType]map[string]config.JumpServiceDestination)

	if filepath != "" {
		conf := []config.JumpServiceDestination{}

		data, err := os.ReadFile(filepath)
		if err != nil {
			return nil, fmt.Errorf("read file: %w", err)
		}

		err = yaml.Unmarshal(data, &conf)
		if err != nil {
			return nil, fmt.Errorf("unmarshal: %w", err)
		}

		for _, item := range conf {
			typ := jpent.DestinationType(strings.ToLower(item.Type)) // TODO: string.ToLower to be compatible with old config
			if !slices.Contains(jpent.DestinationTypeValues, typ) {
				return nil, fmt.Errorf("unsupported dest type %s", item.Type)
			}

			if _, ok := m[typ]; !ok {
				m[typ] = make(map[string]config.JumpServiceDestination)
			}

			if _, ok := m[typ][item.Name]; ok {
				return nil, fmt.Errorf("duplicate dest %s", item.Name)
			}

			m[typ][item.Name] = item
		}
	}

	return &DestinationRegistry{
		secretsource: secretsource,
		dests:        m,
	}, nil
}

func (r *DestinationRegistry) LoadConfig(typ jpent.DestinationType, name string) (*api.Config, error) {
	destsInType, ok := r.dests[typ]
	if !ok {
		return nil, fmt.Errorf("unsupported dest type %s", typ)
	}

	dest, ok := destsInType[name]
	if !ok {
		return nil, fmt.Errorf("dest %s not found", name)
	}

	if dest.AWSAuth != nil {
		eksAuth := k8sutil.EKSAuth{
			Region:     dest.AWSAuth.Region,
			EKSName:    dest.K8sName,
			RoleARN:    dest.AWSAuth.AssumeRole,
			ExternalID: dest.AWSAuth.ExternalID,
		}
		config := k8sutil.NewConfigBuilder().
			AddEKSAuth(dest.K8sName, eksAuth).
			AddInsecureCluster(dest.K8sName, dest.K8sEndpoint).
			AddContext(dest.K8sName, api.Context{Cluster: dest.K8sName, AuthInfo: dest.K8sName}).
			CurrentContext(dest.K8sName).
			Build()
		return config, nil
	}

	if dest.GCPAuth != nil {
		config := k8sutil.NewConfigBuilder().
			AddGKEAuth(dest.K8sName).
			AddInsecureCluster(dest.K8sName, dest.K8sEndpoint).
			AddContext(dest.K8sName, api.Context{Cluster: dest.K8sName, AuthInfo: dest.K8sName}).
			CurrentContext(dest.K8sName).
			Build()
		return config, nil
	}

	if dest.AzureAuth != nil {
		azAuth := &k8sutil.AKSAuth{
			TenantID:           dest.AzureAuth.TenantID,
			ClientID:           dest.AzureAuth.ClientID,
			FederatedTokenFile: dest.AzureAuth.FederatedTokenFilepath,
		}
		config := k8sutil.NewConfigBuilder().
			AddAKSAuth(dest.K8sName, azAuth).
			AddInsecureCluster(dest.K8sName, dest.K8sEndpoint).
			AddContext(dest.K8sName, api.Context{Cluster: dest.K8sName, AuthInfo: dest.K8sName}).
			CurrentContext(dest.K8sName).
			Build()
		return config, nil
	}

	if dest.AliCloudAuth != nil {
		acAuth := &k8sutil.ACKAuth{
			ClusterID:   dest.AliCloudAuth.ClusterID,
			ProfileName: dest.AliCloudAuth.ProfileName,
			ProfileFile: dest.AliCloudAuth.ProfileFile,
		}
		config := k8sutil.NewConfigBuilder().
			AddACKAuth(dest.K8sName, acAuth).
			AddInsecureCluster(dest.K8sName, dest.K8sEndpoint).
			AddContext(dest.K8sName, api.Context{Cluster: dest.K8sName, AuthInfo: dest.K8sName}).
			CurrentContext(dest.K8sName).
			Build()
		return config, nil
	}

	if dest.CertAuth != nil {
		certAuth := &k8sutil.CertAuth{}
		if dest.CertAuth.FromSecret != nil {
			certAuthData, err := r.secretsource.GetSecretValue(context.Background(), dest.CertAuth.FromSecret.SecretName)
			if err != nil {
				return nil, fmt.Errorf("failed to get cert data from secret '%s': %w", dest.CertAuth.FromSecret.SecretName, err)
			}

			err = json.Unmarshal([]byte(certAuthData), certAuth)
			if err != nil {
				return nil, fmt.Errorf("failed to unmarshal secret '%s': %w", dest.CertAuth.FromSecret.SecretName, err)
			}
		}

		config := k8sutil.NewConfigBuilder().
			AddCertAuth(dest.K8sName, certAuth).
			AddInsecureCluster(dest.K8sName, dest.K8sEndpoint).
			AddContext(dest.K8sName, api.Context{Cluster: dest.K8sName, AuthInfo: dest.K8sName}).
			CurrentContext(dest.K8sName).
			Build()
		return config, nil
	}

	return nil, fmt.Errorf("unsupported k8s type")
}

func (r *DestinationRegistry) RegisteredDestinations(typ jpent.DestinationType) []string {
	destsInType, ok := r.dests[typ]
	if !ok {
		return nil
	}

	var names []string
	for _, dest := range destsInType {
		names = append(names, dest.Name)
	}
	return names
}
