package common

import (
	"context"
	"fmt"

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

	clusteragent "github.com/tidbcloud/eks-provider/controllers/utils/cluster_agent"
	"github.com/tidbcloud/eks-provider/controllers/workflow/action"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

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

// pauseReconciliation
type pauseReconciliation struct {
	cli client.Client
}

type pauseOrResumeReconciliationInputs 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 tidbcluster"`
}

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

func NewPauseReconciliation(cli client.Client) action.Action {
	return &pauseReconciliation{cli: cli}
}

func (p *pauseReconciliation) Name() cloudv1.WorkflowActionName {
	return consts.WfActionPauseReconciliationAction
}

func (p *pauseReconciliation) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &pauseOrResumeReconciliationInputs{}
	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)
	}
	return nil
}

func (p *pauseReconciliation) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &pauseOrResumeReconciliationInputs{}
	_ = mapstructure.Decode(step.Inputs.Object, inputs)

	if inputs.Type == "tidbcluster" {
		return p.pauseTC(ctx, inputs)
	}

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

	annotations := object.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}
	if reason, ok := annotations[cloudv1.AnnoPauseReconciliationReason]; ok {
		return fmt.Errorf("another one is pausing this cr, reason: %s", reason)
	}

	switch inputs.Type {
	case "cluster":
		cluster := object.(*cloudv1.Cluster)
		cluster.Spec.Paused = pointer.Bool(true)
	case "configchange":
		cc := object.(*cloudv1.ConfigChange)
		cc.Spec.Paused = pointer.Bool(true)
	case "upgradeoperation":
		uo := object.(*cloudv1.UpgradeOperation)
		uo.Spec.Paused = true
	default:
		return cloudv1.NewWFNonRetriableError(fmt.Errorf("invalid type: %s", inputs.Type))
	}
	annotations[cloudv1.AnnoPauseReconciliationReason] = fmt.Sprintf("paused by workflow: %s", wf.NamespacedName())
	object.SetAnnotations(annotations)
	return p.cli.Update(ctx, object)
}

func (p *pauseReconciliation) pauseTC(ctx context.Context, inputs *pauseOrResumeReconciliationInputs) error {
	logger := log.FromContext(ctx)

	cluster := &cloudv1.Cluster{}
	if err := p.cli.Get(ctx, client.ObjectKey{Namespace: inputs.Namespace, Name: inputs.Name}, cluster); err != nil {
		if apierrors.IsNotFound(err) {
			return cloudv1.NewWFNonRetriableError(fmt.Errorf("cluster %s/%s not found", inputs.Namespace, inputs.Name))
		}
		return fmt.Errorf("failed to get cluster %s/%s: %w", inputs.Namespace, inputs.Name, err)
	}

	cli, err := clusteragent.NewClient(cluster)
	if err != nil {
		return fmt.Errorf("failed to create cluster-agent client: %w", err)
	}

	resp, err := cli.PauseTC(ctx, &apiv1.PauseTCReq{
		ClusterId: inputs.Name,
		All:       true,
	})
	if err != nil {
		return fmt.Errorf("failed to pause all tc: %w", err)
	}
	logger.Info("paused all tc", "cluster", inputs.Name, "pausedTcNames", resp.PausedTcNames)
	return nil
}
