package dataplane

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"

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

	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"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

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

type checkPodStatusInputs struct {
	DryRun      bool   `mapstructure:"dryRun" json:"dryRun"`
	Namespace   string `mapstructure:"namespace" json:"namespace" validate:"required"`
	ClusterName string `mapstructure:"clusterName" json:"clusterName" validate:"required"`
	// ComponentName is the type of the component
	ComponentType string `mapstructure:"componentType" json:"componentType" validate:"required"`
	ExpectState   string `mapstructure:"expectState" json:"expectState" validate:"required"`
}

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

func NewCheckPodStatus(cli client.Client) action.Action {
	return &checkPodStatus{cli: cli}
}

func (p *checkPodStatus) Name() cloudv1.WorkflowActionName {
	return consts.WfActionCheckPodStatusAction
}

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

// Handle checkPodStatus ensures specific pods are at certain Phase.
func (p *checkPodStatus) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	stepStatus, _ := wf.StepStatus(ctx, step.Name)

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

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

	cli, err := clusteragent.NewClient(cluster)
	if err != nil {
		return fmt.Errorf("create cluster-agent client: %w", err)
	}
	listPods, err := cli.ListPods(ctx, inputs.ClusterName)
	if err != nil {
		return fmt.Errorf("failed list pods: %w", err)
	}

	if len(listPods.Pods) <= 0 {
		// Skip phase check if no pods found.
		return nil
	}

	outputs := map[string]string{}

	allPassed := true
	for _, pod := range listPods.Pods {
		if pod.ComponentType != convertComponentTypeToApiCommon(cloudv1.ComponentType(inputs.ComponentType)) {
			continue
		}

		outputs[pod.K8SMetadata.Name] = pod.Phase

		if corev1.PodPhase(pod.Phase) != corev1.PodPhase(inputs.ExpectState) {
			allPassed = false
		}
	}

	// pass when all passed or dry_run is set.
	if !allPassed && !inputs.DryRun {
		return cloudv1.NewWFNonRetriableError(fmt.Errorf("terminated for unexpected pod status: expected %s, got %v", inputs.ExpectState, outputs))
	}

	return stepStatus.UpdateOutput(ctx, outputs)
}
