package common

import (
	"context"
	"fmt"

	"github.com/mitchellh/mapstructure"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	"github.com/tidbcloud/eks-provider/controllers/workflow/action"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

var (
	AnnotationMutatableCRTypes = map[string]client.Object{
		"cluster":          &cloudv1.Cluster{},
		"configchange":     &cloudv1.ConfigChange{},
		"upgradeoperation": &cloudv1.UpgradeOperation{},
	}
)

type mutateAnnotationsAction struct {
	cli client.Client
}

type mutateAnnotationsInputs struct {
	Namespace   string            `mapstructure:"namespace" json:"namespace" validate:"required"`
	Name        string            `mapstructure:"name" json:"name" validate:"required"`
	Type        string            `mapstructure:"type" json:"type" validate:"required,oneof=cluster configchange upgradeoperation"`
	Annotations map[string]string `mapstructure:"annotations" json:"annotations" validate:"required"`
}

var (
	_ action.Action = &mutateAnnotationsAction{}
)

func NewMutateAnnotationsAction(cli client.Client) action.Action {
	return &mutateAnnotationsAction{cli: cli}
}

func (a *mutateAnnotationsAction) Name() cloudv1.WorkflowActionName {
	return consts.WorkflowActionMutateAnnotations
}

func (a *mutateAnnotationsAction) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &mutateAnnotationsInputs{}
	if err := mapstructure.Decode(step.Inputs.Object, inputs); err != nil {
		return fmt.Errorf("decode inputs: %w", err)
	}
	if err := validate.Struct(inputs); err != nil {
		return fmt.Errorf("validate inputs: %w", err)
	}
	_, ok := AnnotationMutatableCRTypes[inputs.Type]
	if !ok {
		return cloudv1.NewWFNonRetriableError(fmt.Errorf("unsupported CR type: %s", inputs.Type))
	}
	return nil
}

func (a *mutateAnnotationsAction) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	logger := log.FromContext(ctx)

	inputs := &mutateAnnotationsInputs{}
	_ = mapstructure.Decode(step.Inputs.Object, inputs)

	obj := AnnotationMutatableCRTypes[inputs.Type].DeepCopyObject().(client.Object)
	if err := a.cli.Get(ctx, client.ObjectKey{Namespace: inputs.Namespace, Name: inputs.Name}, obj); err != nil {
		if apierrors.IsNotFound(err) {
			return cloudv1.NewWFNonRetriableError(fmt.Errorf("%s %s not found", inputs.Type, inputs.Name))
		}
		return fmt.Errorf("get %s: %w", inputs.Type, err)
	}

	annotations := obj.GetAnnotations()
	if annotations == nil {
		annotations = make(map[string]string)
	}
	for k, v := range inputs.Annotations {
		if v == "-" {
			delete(annotations, k)
		} else {
			annotations[k] = v
		}
	}
	obj.SetAnnotations(annotations)
	if err := a.cli.Update(ctx, obj); err != nil {
		return fmt.Errorf("update %s: %w", inputs.Type, err)
	}

	logger.Info("annotations mutated", "type", inputs.Type, "name", inputs.Name, "annotations", inputs.Annotations)
	return nil
}
