package dataplane

import (
	"context"
	"fmt"
	"time"

	"github.com/mitchellh/mapstructure"
	"github.com/samber/lo"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

// waitPodPhase
type waitPodPhase struct {
	cli            client.Client
	cachedProvider dataplane.EnvProviderManager
}

type waitPodReadyInputs struct {
	Namespace   string `mapstructure:"namespace" json:"namespace" validate:"required"`
	ClusterName string `mapstructure:"clusterName" json:"clusterName" validate:"required"`
	PodName     string `mapstructure:"podName" json:"podName" validate:"required"`

	Phase string `mapstructure:"phase" json:"phase" validate:"required"`
	// EnsureCreatedAfter is used to ensure the pod has been created after the specified time.
	// It should be in RFC3339 format or a variable in format '${fromCategory}.{fromStepName}.{fromWhere}.{fromKey}' likes
	// '$steps.step1.status.beginAt'
	EnsureCreatedAfter string `mapstructure:"ensureCreatedAfter" json:"ensureCreatedAfter" validate:"-"`
}

// nolint:unused
type waitPodReadyOutputs struct {
	NodeName string `mapstructure:"nodeName"`
}

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

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

func (p *waitPodPhase) Name() cloudv1.WorkflowActionName {
	return consts.WfActionWaitPodPhaseAction
}

func (p *waitPodPhase) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &waitPodReadyInputs{}
	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 *waitPodPhase) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &waitPodReadyInputs{}
	_ = mapstructure.Decode(step.Inputs.Object, inputs)
	stepStatus := lo.Must(wf.StepStatus(ctx, step.Name))
	var ensureCreatedAfter time.Time
	if inputs.EnsureCreatedAfter != "" {
		var err error
		ensureCreatedAfter, err = time.Parse(time.RFC3339, inputs.EnsureCreatedAfter)
		if err != nil {
			return cloudv1.NewWFNonRetriableError(fmt.Errorf("parse ensureCreatedAfter: %w", err))
		}
	}

	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)
	}

	remoteCli, err := p.cachedProvider.GetClient(ctx, cluster)
	if err != nil {
		return fmt.Errorf("get dataplane cli: %w", err)
	}
	pod := &corev1.Pod{}
	// the pod may be in creating, so don't mark WF as failed if the pod is not found.
	if err := remoteCli.Get(ctx, client.ObjectKey{Namespace: cluster.PrefixedId(), Name: inputs.PodName}, pod); err != nil {
		return fmt.Errorf("get pod: %w", err)
	}
	if inputs.EnsureCreatedAfter != "" && pod.CreationTimestamp.Time.Before(ensureCreatedAfter) {
		return fmt.Errorf("pod %s is created before %s", pod.Name, inputs.EnsureCreatedAfter)
	}
	if string(pod.Status.Phase) != inputs.Phase {
		return fmt.Errorf("pod %s phase, expect=%s,actual=%s", pod.Name, inputs.Phase, pod.Status.Phase)
	}
	return stepStatus.UpdateOutput(ctx, map[string]string{
		"nodeName": pod.Spec.NodeName,
	})
}
