package imports

import (
	"context"
	"fmt"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	cloudImports "github.com/tidbcloud/infra-api/controllers/imports"
	"github.com/tidbcloud/infra-api/pkg/gardener/nodegroup"
	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	nameutil "github.com/tidbcloud/shared-provider/pkg/utils/name"
	"github.com/tidbcloud/shared-provider/pkg/utils/namespace"
)

const awsWebIdentityTokenFile = "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"

type ImportEnv struct {
	Env    string // environment name, "dev", "staging", "prod", etc.
	Client client.Client
}

var _ cloudImports.Env = &ImportEnv{}

// TCName implement the Env interface.
func (e *ImportEnv) TCName(clusterName string) string {
	return "db" + clusterName
}

// GetClusterCli implement the Env interface.
func (e *ImportEnv) GetClusterCli(ctx context.Context, network *cloudv1.Network) (client.Client, error) {
	return nil, nil
}

// GetClusterConfig implements the Env interface
// It is not used in shared tier
func (e *ImportEnv) GetClusterConfig(
	ctx context.Context,
	network *cloudv1.Network,
	cluster *cloudv1.Cluster,
) (*rest.Config, error) {
	return nil, nil
}

// StorageClassName implement the Env interface.
func (e *ImportEnv) StorageClassName() string {
	return types.DefaultStorageClassName
}

// PreCreateJobHook implement the Env interface.
func (e *ImportEnv) PreCreateJobHook(
	net *cloudv1.Network,
	_ *cloudv1.Cluster,
	im *cloudv1.Import,
	job *batchv1.Job,
) error {
	switch net.Spec.Provider {
	case types.ProviderTypeAWSSharedTier:
		if im.Spec.AccessConfig.RoleArn != "" {
			// env take first compare with config file.
			// we want to let SDK(lighting) use the config file.
			// so set this as empty to avoid mutating admission controller set this env.
			for i := 0; i < len(job.Spec.Template.Spec.Containers); i++ {
				job.Spec.Template.Spec.Containers[i].Env = append(
					job.Spec.Template.Spec.Containers[i].Env,
					corev1.EnvVar{Name: "AWS_WEB_IDENTITY_TOKEN_FILE", Value: ""},
				)
				job.Spec.Template.Spec.Containers[i].Env = append(
					job.Spec.Template.Spec.Containers[i].Env,
					corev1.EnvVar{Name: "AWS_ROLE_ARN", Value: ""},
				)
			}
		}

		job.Spec.Template.Spec.ServiceAccountName = nameutil.GetImportServiceAccountName(net.Name)
		job.Spec.Template.Spec.HostNetwork = false
	case types.ProviderTypeGCPSharedTier:
		job.Spec.Template.Spec.HostNetwork = false
	}
	return nil
}

func (e *ImportEnv) Credentials(ctx context.Context, net *cloudv1.Network, im *cloudv1.Import) ([]byte, error) {
	switch net.Spec.Provider {
	case types.ProviderTypeAWSSharedTier:
		if im.Spec.AccessConfig.RoleArn == "" {
			return nil, nil
		}

		// nolint:lll // too long comments
		// ref https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc
		// will work like this with this config:
		// oidc provider ---AssumeRoleWithWebIdentity---> net.Status.Import.RoleARN ---AssumeRole---> Spec.AccessConfig.RoleArn

		format := `[profile web-identity]
role_arn = %s
web_identity_token_file = %s

[default]
role_arn = %s
external_id = %s
source_profile = web-identity
`

		str := fmt.Sprintf(format,
			net.Status.Import.RoleARN,
			awsWebIdentityTokenFile,
			im.Spec.AccessConfig.RoleArn,
			net.Status.IAM.ExternalId,
		)
		return []byte(str), nil
	case types.ProviderTypeGCPSharedTier:
		projectID := labels.GetProjectFromLabels(net.Labels)
		ns := namespace.GetOwnedNamespaceNameFromNetwork(net)
		name := nameutil.GetImportGCPServiceAccountName(e.Env, projectID)
		objectKey := client.ObjectKey{Namespace: ns, Name: name}
		var keySecret corev1.Secret
		if err := e.Client.Get(ctx, objectKey, &keySecret); err != nil {
			return nil, err
		}
		return keySecret.Data[types.CnrmSACredentialCmKey], nil
	}

	return nil, nil
}

// CreateNodeGroup implement the Env interface.
func (e *ImportEnv) CreateNodeGroup(ctx context.Context, network *cloudv1.Network,
	ngs *nodegroup.Set) (map[string]string, error) {
	return map[string]string{}, nil
}

// DeleteNodeGroup implement the Env interface.
func (e *ImportEnv) DeleteNodeGroup(ctx context.Context, network *cloudv1.Network, ngs *nodegroup.Set) error {
	return nil
}
