package task

import (
	"context"
	"errors"
	"fmt"
	"maps"
	"time"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type AppBox struct {
	*BaseTask
}

func NewAppBox(ctx *Context) *AppBox {
	return &AppBox{BaseTask: &BaseTask{Context: ctx}}
}

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

func (t *AppBox) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if t.DataPlane.Status.AppBox != nil && t.DataPlane.Status.AppBox.Name != "" {
		return t.syncLabels(ctx, t.DataPlane.Status.AppBox)
	}

	appbox := &v1alpha1.APPBox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.GetSubResourceName(),
			Namespace: t.GetSubResourceNamespace(),
			// add labels project，tennetid，region
			Labels:      t.desiredLabels(),
			Annotations: t.desiredAnnotations(),
		},
		Spec: v1alpha1.APPBoxSpec{
			Provider: t.Config.Provider,
			Region:   t.Config.RegionConfig.Name,
			KubernetesConfig: v1alpha1.KubernetesConfig{
				Version:         t.Config.APPBoxConfig.K8sVersion,
				ServiceCidr:     t.DataPlane.Spec.ServiceCidr,
				AddonTemplateID: t.TmplConfig.GetDefaultAddonTemplateId(),
				InfraTemplateID: t.TmplConfig.GetDefaultInfraTemplateIdWithVersion(
					t.Config.APPBoxConfig.K8sVersion),
				DedicatedTemplateID: t.TmplConfig.GetDefaultDedicatedTemplateIdWithVersion(
					t.Config.APPBoxConfig.K8sVersion),
				DPTemplateID: t.TmplConfig.GetDefaultDPTemplateIdWithVersion(t.Config.APPBoxConfig.K8sVersion),
				// DO NOT set AdminNodeConfig here, leave the CR field for manual modification at per CR level
				// AdminNodeConfig: v1alpha1.NodeConfig{},
			},
			PCA: t.DataPlane.Spec.PCA,
			VPCRef: &v1alpha1.Reference{
				Name:      t.DataPlane.Status.Vpc.Name,
				Namespace: t.DataPlane.Status.Vpc.Namespace,
			},
		},
	}
	if err := t.AppBoxRW.Create(ctx, appbox); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
		t.Logger.Info("appbox already exists", zap.String("appbox_name", appbox.Name))
	}

	t.DataPlane.Status.AppBox = &v1alpha1.Reference{
		Namespace: appbox.Namespace,
		Name:      appbox.Name,
	}
	return deploy.Done()
}

func (t *AppBox) desiredAnnotations() map[string]string {
	if t.DataPlane.GetAnnotations()[v1alpha1.AnnotationKeyEnableCapacityPlanner] != "true" {
		return nil
	}
	return map[string]string{v1alpha1.AnnotationKeyEnableCapacityPlanner: "true"}
}

func (t *AppBox) desiredLabels() map[string]string {
	return util.MergeMaps(t.Config.ResourceLabels, t.DataPlane.Spec.CloudTags, map[string]string{
		v1alpha1.RegionKey:      t.Config.RegionConfig.Name,
		v1alpha1.VPCNameKey:     t.DataPlane.Status.Vpc.Name,
		v1alpha1.ManageKey:      v1alpha1.ManageFull,
		v1alpha1.DataPlaneIDKey: t.DataPlane.Name,
	})
}

func (t *AppBox) syncLabels(ctx context.Context, ref *v1alpha1.Reference) deploy.Result {
	appbox := &v1alpha1.APPBox{}
	if err := t.AppBoxRW.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, appbox); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	labels := mergeDesiredLabels(appbox.Labels, t.desiredLabels())
	if maps.Equal(appbox.Labels, labels) {
		return deploy.Done()
	}
	appbox.Labels = labels
	if err := t.AppBoxRW.Update(ctx, appbox); err != nil {
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *AppBox) PostCreate(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.AppBox == nil {
		return deploy.Error(errors.New("appbox is nil in status"))
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.AppBox.Namespace,
		Name:      t.DataPlane.Status.AppBox.Name,
	}
	appbox := new(v1alpha1.APPBox)
	if err := t.AppBoxRW.Get(ctx, key, appbox); err != nil {
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}

	if appbox.Status.Phase == v1alpha1.APPBoxPhaseFailed {
		return deploy.Panic(fmt.Errorf("appbox %s is failed", appbox.Name))
	}
	if appbox.Status.Phase != v1alpha1.APPBoxPhaseActive {
		return deploy.Error(fmt.Errorf("appbox %s is not active, phase is %s",
			appbox.Name, appbox.Status.Phase)).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *AppBox) Delete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.AppBox == nil {
		return deploy.Done()
	}

	appbox := &v1alpha1.APPBox{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: t.DataPlane.Status.AppBox.Namespace,
			Name:      t.DataPlane.Status.AppBox.Name,
		},
	}
	if err := t.AppBoxRW.Delete(ctx, appbox); err != nil {
		if !apierrors.IsNotFound(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
	}
	return deploy.Done()
}

func (t *AppBox) PostDelete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.AppBox == nil {
		return deploy.Done()
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.AppBox.Namespace,
		Name:      t.DataPlane.Status.AppBox.Name,
	}
	appbox := new(v1alpha1.APPBox)
	if err := t.AppBoxRW.Get(ctx, key, appbox); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Error(fmt.Errorf("appbox %s is not deleted", appbox.Name)).
		WithRequeuAfter(requeueAfterDuration)
}
