package dataplane

import (
	"context"
	"fmt"
	"strings"

	"github.com/mitchellh/mapstructure"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/samber/lo"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"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"
	"github.com/tidbcloud/infra-provider/pkg/dataplane"
)

// restartComponent restarts a tc pod by calling cluster-agent's API
type restartComponent struct {
	cli            client.Client
	cachedProvider dataplane.EnvProviderManager
}

type restartComponentInputs struct {
	Namespace   string `mapstructure:"namespace" json:"namespace" validate:"required"`
	ClusterName string `mapstructure:"clusterName" json:"clusterName" validate:"required"`
	// TargetName component targetName for example 'tidb'
	TargetName string `mapstructure:"targetName" json:"targetName" validate:"required"`
	// TargetGroup if is specified, only restart the components of the TiDBCluster;
	// If it's empty, restart the components across all TiDBClusters.
	TargetGroup string `mapstructure:"targetGroup" json:"targetGroup" validate:"omitempty"`
	Force       bool   `mapstructure:"force" json:"force" validate:"omitempty"`
}

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

func NewRestartComponent(cli client.Client, cachedProvider dataplane.EnvProviderManager) action.Action {
	return &restartComponent{cli: cli, cachedProvider: cachedProvider}
}

func (p *restartComponent) Name() cloudv1.WorkflowActionName {
	return consts.WfActionRestartComponentAction
}

func (p *restartComponent) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &restartComponentInputs{}
	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 *restartComponent) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	logger := log.FromContext(ctx)
	inputs := &restartComponentInputs{}
	lo.Must0(mapstructure.Decode(step.Inputs.Object, inputs))

	// currently use COMPONENT as the default type of this action.
	compType := apiv1.RestartTargetType_COMPONENT
	compName := inputs.TargetName

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

	remoteCli, err := p.cachedProvider.GetClient(ctx, cluster)
	if err != nil {
		return fmt.Errorf("get dataplane cli: %w", err)
	}

	tcs := &v1alpha1.TidbClusterList{}
	// If targetGroup is specified, get the target tidb-cluster.
	if inputs.TargetGroup != "" {
		tc := v1alpha1.TidbCluster{}
		err = remoteCli.Get(ctx, client.ObjectKey{
			Name:      inputs.TargetGroup,
			Namespace: cluster.PrefixedId(),
		}, &tc)

		tcs.Items = append(tcs.Items, tc)

	} else {
		// else, list all tidb-clusters.
		err = remoteCli.List(ctx, tcs, client.InNamespace(cluster.PrefixedId()), client.MatchingLabels{
			cloudv1.LabelUsedFor: cloudv1.UsedForCluster,
		})
	}
	if err != nil {
		return fmt.Errorf("list %s/tidb-clusters: %w", inputs.Namespace, err)
	}

	// For each tidb-cluster, call cluster-agent.Restart to rolling restart the certain component.
	var details []string
	for _, tc := range tcs.Items {
		tcName := tc.GetName()

		req := &apiv1.RestartReq{
			// the format of idempotency key is same as operationId
			OperationId: wf.Labels[cloudv1.IdempotencyKey],
			ClusterId:   inputs.ClusterName,
			TargetGroup: tcName,
			TargetType:  compType,
			TargetName:  compName,
			Force:       inputs.Force,
		}

		resp, err := cli.Restart(ctx, req)
		if err != nil {
			if resp != nil {
				logger.Info("restart pod", "tc", tcName, "message", resp.Message)
			}
			details = append(details, tcName, fmt.Sprintf("[tc/%s: %s]", tcName, err))
		}
	}

	if len(details) > 0 {
		return fmt.Errorf("restart tc error: %s", strings.Join(details, "; "))
	}

	return nil
}
