package jumper

import (
	"context"
	"fmt"
	"time"

	authenticationv1 "k8s.io/api/authentication/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8stypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd/api"

	jpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"
)

const (
	JumpReadOnlyRoleName         = "jump-readonly"
	JumpReadWriteRoleName        = "jump-readwrite"
	JumpAdminRoleName            = "jump-admin"
	JumpGatewayReadOnlyRoleName  = "jump-gateway-readonly"
	JumpGatewayReadWriteRoleName = "jump-gateway-readwrite"
	JumpGatewayAdminRoleName     = "jump-gateway-admin"
)

func JumpRoleName(priv jpent.JumpPrivilege) string {
	switch priv {
	case jpent.JumpPrivilegeReadOnly:
		return JumpReadOnlyRoleName
	case jpent.JumpPrivilegeReadWrite:
		return JumpReadWriteRoleName
	case jpent.JumpPrivilegeAdmin:
		return JumpAdminRoleName
	default:
		panic("unreachable")
	}
}

func JumpGatewayRoleName(priv jpent.JumpPrivilege) string {
	switch priv {
	case jpent.JumpPrivilegeReadOnly:
		return JumpGatewayReadOnlyRoleName
	case jpent.JumpPrivilegeReadWrite:
		return JumpGatewayReadWriteRoleName
	case jpent.JumpPrivilegeAdmin:
		return JumpGatewayAdminRoleName
	default:
		panic("unreachable")
	}
}

func OverwriteJumpRoleName(old, new string) string {
	prio := map[string]int{
		JumpReadOnlyRoleName:  1,
		JumpReadWriteRoleName: 2,
		JumpAdminRoleName:     3,
	}

	if prio[new] > prio[old] {
		return new
	}
	return old
}

func OverwriteJumpGatewayRoleName(old, new string) string {
	prio := map[string]int{
		JumpGatewayReadOnlyRoleName:  1,
		JumpGatewayReadWriteRoleName: 2,
		JumpGatewayAdminRoleName:     3,
	}

	if prio[new] > prio[old] {
		return new
	}
	return old
}

// generateToken requests a new service account token.
func GenerateToken(ctx context.Context, cli kubernetes.Interface, saKey k8stypes.NamespacedName, param types.GenerateTokenParam) (*types.Token, error) {
	expireDur := time.Until(param.ExpireAt)
	request := &authenticationv1.TokenRequest{
		Spec: authenticationv1.TokenRequestSpec{
			Audiences:         []string{},
			ExpirationSeconds: pointer.Pointer(int64(expireDur / time.Second)),
		},
	}
	resp, err := cli.CoreV1().ServiceAccounts(saKey.Namespace).CreateToken(ctx, saKey.Name, request, metav1.CreateOptions{})
	if err != nil {
		return nil, fmt.Errorf("create token failed: %w", err)
	}

	token := &types.Token{
		Data:      resp.Status.Token,
		ExpiredAt: resp.Status.ExpirationTimestamp.Time, // may be shorter than requested
	}
	return token, nil
}

// retrieveToken retrieves the token of a service account.
func RetrieveToken(ctx context.Context, cli kubernetes.Interface, saKey k8stypes.NamespacedName, param types.GenerateTokenParam) (*types.Token, error) {
	sa, err := cli.CoreV1().ServiceAccounts(saKey.Namespace).Get(ctx, saKey.Name, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("get service account failed: %w", err)
	}
	if len(sa.Secrets) == 0 {
		return nil, fmt.Errorf("no secret of service account")
	}

	secret, err := cli.CoreV1().Secrets(saKey.Namespace).Get(ctx, sa.Secrets[0].Name, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("get secret failed: %w", err)
	}

	token, exist := secret.Data["token"]
	if !exist {
		return nil, fmt.Errorf("no token in secret")
	}

	return &types.Token{
		Data:      string(token),
		ExpiredAt: time.Time{},
	}, nil
}

// ExtractACKClusterIDFromKubeconfig extracts the ACK cluster ID from the given kubeconfig.
func ExtractACKClusterIDFromKubeconfig(raw *api.Config) (string, error) {
	clusterID := ""
	for _, user := range raw.AuthInfos {
		if user.Exec != nil {
			for i, arg := range user.Exec.Args {
				if arg == "--cluster-id" && i+1 < len(user.Exec.Args) {
					clusterID = user.Exec.Args[i+1]
					break
				}
			}
		}
	}

	if clusterID == "" {
		return "", fmt.Errorf("cannot find ack cluster id in kubeconfig")
	}
	return clusterID, nil
}
