package eks

import (
	"context"

	"github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/crossplane/crossplane-runtime/pkg/resource"
	"k8s.io/apimachinery/pkg/util/errors"

	"github.com/tidbcloud/infra-provider/pkg/scheme"
)

type ProviderConfig struct {
	task.BaseTask
}

func NewProviderConfig(ctx *task.Context) *ProviderConfig {
	return &ProviderConfig{
		BaseTask: task.BaseTask{Context: ctx},
	}
}

func (t *ProviderConfig) Name() string {
	return "ProviderConfig"
}

func (t *ProviderConfig) Create(ctx context.Context) deploy.Result {
	if t.IsLegacy() {
		// legacy network is available, use the ProviderConfig created by Network Controller.
		return deploy.Done()
	}

	// we should get the REAL assumed IAM role ARN even if the cluster-controller runs in dryrun mode.
	assumeRoleARN := t.GetAWSAssumeRoleARNForXP()
	config := &v1beta1.ProviderConfig{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.GetAWSProviderConfigName(),
			Labels: map[string]string{"tidbcloud-namespace": t.Cluster.Namespace},
			Annotations: map[string]string{
				"assume-role": assumeRoleARN,
				"external-id": t.GetAWSAssumeRoleExternalIDForXP(),
			},
		},
		Spec: v1beta1.ProviderConfigSpec{
			AssumeRoleARN: pointer.String(assumeRoleARN),
			ExternalID:    pointer.String(t.GetAWSAssumeRoleExternalIDForXP()),
			Credentials: v1beta1.ProviderCredentials{
				Source: v1.CredentialsSourceInjectedIdentity,
			},
		},
	}
	return deploy.PanicOrDone(k8s.Apply(ctx, t.Local, config))
}

func (t *ProviderConfig) Delete(ctx context.Context) deploy.Result {
	if t.IsLegacy() {
		return deploy.Done()
	}

	// only used for 3-layer cluster
	if err := t.deleteAll(ctx); err != nil {
		return deploy.Error(err)
	}

	config := &v1beta1.ProviderConfig{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.GetAWSProviderConfigName(),
		},
	}
	return deploy.ErrorOrDone(k8s.DeleteObjectCompleted(ctx, t.Local, config))
}

func (t *ProviderConfig) deleteAll(ctx context.Context) error {
	errList := []error{}
	usl := resource.MustCreateObject(v1beta1.ProviderConfigUsageListGroupVersionKind, scheme.Scheme).(resource.ProviderConfigUsageList)
	if err := t.Local.List(ctx, usl, client.MatchingLabels{
		v1.LabelKeyProviderName: t.GetAWSProviderConfigName(),
	}); err != nil {
		errList = append(errList, err)
	}

	for _, item := range usl.GetItems() {
		ref := item.GetResourceReference()
		obj := resource.MustCreateObject(ref.GroupVersionKind(), scheme.Scheme).(client.Object)
		obj.SetName(ref.Name)
		if err := t.Local.Delete(ctx, obj); err != nil {
			errList = append(errList, err)
		}
	}

	if len(errList) != 0 {
		return errors.NewAggregate(errList)
	}

	return nil
}
