package br

import (
	"context"
	"fmt"

	"github.com/mitchellh/mapstructure"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

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

// createNodePool creates a nodepool to run workload
var _ action.Action = &createNodePool{}

type createNodePool struct {
	cli client.Client
}

type createNodePoolInputs struct {
	Name           string            `mapstructure:"name" json:"name" validate:"required"`
	InstanceType   string            `mapstructure:"instanceType" json:"instanceType" validate:"required"`
	Region         string            `mapstructure:"region" json:"region" validate:"required"`
	Zones          []string          `mapstructure:"zones" json:"zones" validate:"required"`
	KubeClusterID  string            `mapstructure:"kubeClusterID" json:"kubeClusterID" validate:"required"`
	NodeCount      int32             `mapstructure:"nodeCount" json:"nodeCount" validate:"omitempty"` // omit nodeCount will enable autoscaling for nodepool
	Labels         map[string]string `mapstructure:"labels" json:"labels" validate:"omitempty"`
	Tags           map[string]string `mapstructure:"tags" json:"tags" validate:"omitempty"`
	RootDiskSizeGB int64             `mapstructure:"rootDiskSizeGB" json:"rootDiskSizeGB" validate:"omitempty"`
	Taints         []corev1.Taint    `mapstructure:"taints" json:"taints" validate:"omitempty"`
	NodeSubnets    []string          `mapstructure:"nodeSubnets" json:"nodeSubnets" validate:"omitempty"`
	PodSubnets     []string          `mapstructure:"podSubnets" json:"podSubnets" validate:"omitempty"`
	ProviderType   string            `mapstructure:"providerType" json:"providerType" validate:"omitempty"`
	ProviderConfig string            `mapstructure:"providerConfig" json:"providerConfig" validate:"omitempty"`
}

// nolint: unused
type createNodePoolOutputs struct {
	NodePoolID string `mapstructure:"nodePoolID" validate:"required"`
}

func NewCreateNodePool(cli client.Client) action.Action {
	return &createNodePool{cli: cli}
}

func (a *createNodePool) Name() cloudv1.WorkflowActionName {
	return "CreateNodePoolAction"
}

func (a *createNodePool) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &createNodePoolInputs{}
	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 (a *createNodePool) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	logger := log.FromContext(ctx)
	stepStatus, _ := wf.StepStatus(ctx, step.Name)

	inputs := &createNodePoolInputs{}
	_ = mapstructure.Decode(step.Inputs.Object, inputs)
	var np provider.NodePoolProvider
	switch inputs.ProviderType {
	case consts.ProviderTypeAKS:
		np = provider.NewAzureNodePool(a.cli, inputs.ProviderConfig)
	case consts.ProviderTypeEKS, consts.ProviderTypeGKE:
		return fmt.Errorf("nodepool provider unimplemented for %s", inputs.ProviderType)
	default:
		return fmt.Errorf("unknown provider type %s", inputs.ProviderType)
	}
	nodepool := &provider.NodePool{
		Name:           inputs.Name,
		Arch:           consts.AMD64ArchName,
		Region:         inputs.Region,
		Zones:          inputs.Zones,
		NodeCount:      inputs.NodeCount,
		InstanceType:   inputs.InstanceType,
		RootDiskSizeGB: inputs.RootDiskSizeGB,
		KubeClusterID:  inputs.KubeClusterID,
		NodeSubnets:    inputs.NodeSubnets,
		PodSubnets:     inputs.PodSubnets,
		Taints:         inputs.Taints,
		Labels:         inputs.Labels,
		Tags:           inputs.Tags,
	}
	logger.Info("create nodepool", "nodepool", inputs.Name, "provider", np.GetProviderName())
	nodepoolID, err := np.CreateNodePool(ctx, nodepool)
	if err != nil {
		return err
	}
	outputs := map[string]string{
		"nodePoolID": nodepoolID,
	}
	return stepStatus.UpdateOutput(ctx, outputs)
}
