package tinkerbell

import (
	"context"

	"github.com/go-logr/logr"
	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"

	tinkerbellv1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/capt/v1beta1"
	"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
	"github.com/aws/eks-anywhere/pkg/cluster"
	"github.com/aws/eks-anywhere/pkg/clusterapi"
	yamlcapi "github.com/aws/eks-anywhere/pkg/clusterapi/yaml"
	"github.com/aws/eks-anywhere/pkg/constants"
	"github.com/aws/eks-anywhere/pkg/yamlutil"
)

// BaseControlPlane represents a CAPI Tinkerbell control plane.
type BaseControlPlane = clusterapi.ControlPlane[*tinkerbellv1.TinkerbellCluster, *tinkerbellv1.TinkerbellMachineTemplate]

// ControlPlane holds the Tinkerbell specific objects for a CAPI Tinkerbell control plane.
type ControlPlane struct {
	BaseControlPlane
	Secrets *corev1.Secret
}

// Objects returns the control plane objects associated with the Tinkerbell cluster.
func (p ControlPlane) Objects() []kubernetes.Object {
	o := p.BaseControlPlane.Objects()
	o = append(o, p.Secrets)

	return o
}

// ControlPlaneSpec builds a Tinkerbell ControlPlane definition based on an eks-a cluster spec.
func ControlPlaneSpec(ctx context.Context, logger logr.Logger, client kubernetes.Client, clusterSpec *cluster.Spec) (*ControlPlane, error) {
	templateBuilder, err := generateTemplateBuilder(clusterSpec)
	if err != nil {
		return nil, errors.Wrap(err, "generating tinkerbell template builder")
	}

	controlPlaneYaml, err := templateBuilder.GenerateCAPISpecControlPlane(
		clusterSpec,
		func(values map[string]interface{}) {
			values["controlPlaneTemplateName"] = clusterapi.ControlPlaneMachineTemplateName(clusterSpec.Cluster)
			values["etcdTemplateName"] = clusterapi.EtcdMachineTemplateName(clusterSpec.Cluster)
		},
	)
	if err != nil {
		return nil, errors.Wrap(err, "generating tinkerbell control plane yaml spec")
	}

	parser, builder, err := newControlPlaneParser(logger)
	if err != nil {
		return nil, err
	}

	err = parser.Parse(controlPlaneYaml, builder)
	if err != nil {
		return nil, errors.Wrap(err, "parsing tinkerbell control plane yaml")
	}

	cp := builder.ControlPlane
	if err = cp.UpdateImmutableObjectNames(ctx, client, GetMachineTemplate, machineTemplateEqual); err != nil {
		return nil, errors.Wrap(err, "updating tinkerbell immutable object names")
	}

	return cp, nil
}

// ControlPlaneBuilder defines the builder for all objects in the CAPI Tinkerbell control plane.
type controlPlaneBuilder struct {
	BaseBuilder  *yamlcapi.ControlPlaneBuilder[*tinkerbellv1.TinkerbellCluster, *tinkerbellv1.TinkerbellMachineTemplate]
	ControlPlane *ControlPlane
}

// BuildFromParsed implements the base yamlcapi.BuildFromParsed and processes any additional objects (secrets) for the Tinkerbell control plane.
func (b *controlPlaneBuilder) BuildFromParsed(lookup yamlutil.ObjectLookup) error {
	if err := b.BaseBuilder.BuildFromParsed(lookup); err != nil {
		return err
	}

	b.ControlPlane.BaseControlPlane = *b.BaseBuilder.ControlPlane
	for _, obj := range lookup {
		if obj.GetObjectKind().GroupVersionKind().Kind == constants.SecretKind {
			b.ControlPlane.Secrets = obj.(*corev1.Secret)
		}
	}

	return nil
}

func newControlPlaneParser(logger logr.Logger) (*yamlutil.Parser, *controlPlaneBuilder, error) {
	parser, baseBuilder, err := yamlcapi.NewControlPlaneParserAndBuilder(
		logger,
		yamlutil.NewMapping(
			"TinkerbellCluster",
			func() *tinkerbellv1.TinkerbellCluster {
				return &tinkerbellv1.TinkerbellCluster{}
			},
		),
		yamlutil.NewMapping(
			"TinkerbellMachineTemplate",
			func() *tinkerbellv1.TinkerbellMachineTemplate {
				return &tinkerbellv1.TinkerbellMachineTemplate{}
			},
		),
	)
	if err != nil {
		return nil, nil, errors.Wrap(err, "building tinkerbell control plane parser")
	}

	err = parser.RegisterMappings(
		yamlutil.NewMapping(constants.SecretKind, func() yamlutil.APIObject {
			return &corev1.Secret{}
		}),
	)
	if err != nil {
		return nil, nil, errors.Wrap(err, "registering tinkerbell control plane mappings in parser")
	}

	builder := &controlPlaneBuilder{
		BaseBuilder:  baseBuilder,
		ControlPlane: &ControlPlane{},
	}

	return parser, builder, nil
}
