package eks

import (
	"context"
	"fmt"
	"time"

	eksv1alpha1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8serrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type Addons struct {
	*task.BaseTask
}

func NewAddons(ctx *task.Context) *Addons {
	return &Addons{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *Addons) Name() string {
	return "EKSAddons"
}

func (t *Addons) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())

	addons, versionExists := t.AddonTmplConfig.EKS[t.APPBox.Spec.KubernetesConfig.Version]
	if !versionExists {
		return deploy.Error(fmt.Errorf("AddonTemplateNotFound for k8s version %s", t.APPBox.Spec.KubernetesConfig.Version))
	}

	// create crossplane addon CR
	// TODO: whether to remove addons CR that are not present in current template.
	errList := make([]error, 0)
	for _, addonConf := range addons {
		addonName := addonConf.AddonName
		addonCRName := namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix(addonName))
		existing := &eksv1alpha1.Addon{}
		if err := t.Local.Get(ctx, client.ObjectKey{Name: addonCRName}, existing); client.IgnoreNotFound(err) != nil {
			return deploy.Error(fmt.Errorf("get addon %s: %v", addonCRName, err))
		}
		addon := &eksv1alpha1.Addon{
			ObjectMeta: metav1.ObjectMeta{
				Name:   addonCRName,
				Labels: t.Namer.Labels(),
			},
			Spec: eksv1alpha1.AddonSpec{
				ForProvider: eksv1alpha1.AddonParameters{
					AddonName:    pointer.String(addonConf.AddonName),
					AddonVersion: pointer.String(addonConf.AddonVersion),
					Region:       t.APPBox.Spec.Region,
					CustomAddonParameters: eksv1alpha1.CustomAddonParameters{
						ClusterName: pointer.String(t.Cluster.GetName()),
					},
					ConfigurationValues: addonConf.Values,
					ResolveConflicts:    pointer.String("Overwrite"),
				},
				ResourceSpec: xpv1.ResourceSpec{
					ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
				},
			},
		}

		if err := k8s.Apply(ctx, t.Local, addon); err != nil {
			errList = append(errList, fmt.Errorf("create or update addon %s: %v", addon.Name, err))
		}
	}

	return deploy.ErrorOrDone(k8serrors.NewAggregate(errList))
}

// for migrated appbox we do not check status for addon aws-ebs-csi-driver
func (t *Addons) PostCreate(ctx context.Context) deploy.Result {
	addons, exist := t.AddonTmplConfig.EKS[t.APPBox.Spec.KubernetesConfig.Version]
	if !exist {
		return deploy.Error(fmt.Errorf("AddonTemplateNotFound for k8s version %s", t.APPBox.Spec.KubernetesConfig.Version))
	}
	skipAddonCSI := t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipAddonAWSEBSCSIDriver] == "true"
	skipAddonVPCCNI := t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipAddonVPCCNI] == "true"
	skipCoreDNS := t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipAddonCoreDNS] == "true"
	skipKubeProxy := t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipAddonKubeProxy] == "true"
	addonStatus := []v1alpha1.AddonStatus{}
	for _, addonConf := range addons {
		checkStatus := true
		if skipAddonCSI && addonConf.AddonName == "aws-ebs-csi-driver" {
			checkStatus = false
		}
		if skipAddonVPCCNI && addonConf.AddonName == "vpc-cni" {
			checkStatus = false
		}
		if skipCoreDNS && addonConf.AddonName == "coredns" {
			checkStatus = false
		}
		if skipKubeProxy && addonConf.AddonName == "kube-proxy" {
			checkStatus = false
		}
		addon := &eksv1alpha1.Addon{
			ObjectMeta: metav1.ObjectMeta{
				Name: namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix(addonConf.AddonName)),
			},
		}
		if err := t.Local.Get(ctx, client.ObjectKeyFromObject(addon), addon); err != nil {
			return deploy.Error(err)
		}
		status := addon.Status.AtProvider.Status
		if status == nil {
			return deploy.Error(fmt.Errorf("addon components %s is not active", addon.Name))
		}
		if checkStatus && *status != string(eksv1alpha1.AddonStatus_SDK_ACTIVE) {
			return deploy.Error(fmt.Errorf("addon components %s is not active", addon.Name))
		}
		addonStatus = append(addonStatus, v1alpha1.AddonStatus{
			Name:    *addon.Spec.ForProvider.AddonName,
			Version: *addon.Spec.ForProvider.AddonVersion,
			Status:  addon.Status.AtProvider.Status,
		})
	}
	t.APPBox.Status.Addons = addonStatus
	return deploy.Done()
}

func (t *Addons) Delete(ctx context.Context) deploy.Result {
	addons := &eksv1alpha1.AddonList{}
	ops := k8s.ListOpts[*eksv1alpha1.Addon]{
		ObjectList: addons,
		Unpack:     func() []*eksv1alpha1.Addon { return k8s.MapPtr(addons.Items) },
	}

	err := ops.DeleteFilterBy(ctx, t.Local, func(addon *eksv1alpha1.Addon) bool {
		return true
	}, client.MatchingLabels(t.Namer.Labels()))

	if err != nil {
		return deploy.Error(fmt.Errorf("delete addons failed: %v", err))
	}

	return deploy.Done()
}

func (t *Addons) PostDelete(ctx context.Context) deploy.Result {
	addonList := &eksv1alpha1.AddonList{}
	ops := k8s.ListOpts[*eksv1alpha1.Addon]{
		ObjectList: addonList,
		Unpack:     func() []*eksv1alpha1.Addon { return k8s.MapPtr(addonList.Items) },
	}

	addons, err := ops.List(ctx, t.Local, client.MatchingLabels(t.Namer.Labels()))
	if err != nil {
		return deploy.Error(err)
	}
	if len(addons) > 0 {
		return deploy.Error(fmt.Errorf("there are %d addons still exist", len(addons)))
	}
	return deploy.Result{}
}
