package procedure

import (
	"context"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"
	corev1 "k8s.io/api/core/v1"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/util/collections"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	frpsPrivatelinkName = "frps-privatelink"
)

type frpsPrivatelink struct {
	scheme *runtime.Scheme
}

// Deploy deploys resource for the specified tenant.
func (f *frpsPrivatelink) Deploy(ctx context.Context, _, infraCli client.Client, info ProcedureInfo) error {
	frps := &eksv1alpha1.Frpserver{}
	err := infraCli.Get(ctx, types.NamespacedName{Name: namer.UniqueIdentifier(info.eksConnection.ObjectMeta)}, frps)
	if err != nil {
		return err
	}
	if frps.Annotations == nil || frps.Annotations[AnnotationKeyFrpsSvcName] == "" || frps.Annotations[AnnotationKeyFrpsSvcNs] == "" {
		return fmt.Errorf("frps service annotation is not set")
	}
	frpsSvcName, frpsSvcNs := frps.Annotations[AnnotationKeyFrpsSvcName], frps.Annotations[AnnotationKeyFrpsSvcNs]
	pl := getFrpsPrivatelink(info, frpsSvcName, frpsSvcNs)
	_, err = controllerutil.CreateOrUpdate(ctx, infraCli, pl, func() error {
		mutateFrpsPrivatelinkSpec(pl, info, frpsSvcName, frpsSvcNs)
		err := controllerutil.SetControllerReference(info.eksConnection, pl, f.scheme)
		if IsAlreadyOwnedError(err) {
			return nil
		}
		return err
	})
	return err
}

func mutateFrpsPrivatelinkSpec(pl *eksv1alpha1.Privatelink, info ProcedureInfo, frpsSvcName, frpsSvcNs string) {
	pl.Spec.Service = eksv1alpha1.ServiceTarget{
		Target: &corev1.ObjectReference{
			APIVersion: "v1",
			Kind:       "Service",
			Name:       frpsSvcName,
			Namespace:  frpsSvcNs,
		},
	}
	pl.Spec.ClientProviderRef = aws.String(info.ProviderConfigRef)
	pl.Spec.ServerProviderRef = aws.String(info.HostProviderConfigRef)
	pl.Spec.VpcInfo = eksv1alpha1.VpcInfo{
		Region:           info.Region,
		VpcID:            info.VpcID,
		SubnetIDs:        info.SubnetIDs,
		SecurityGroupIDs: info.SecurityGroupIDs,
	}
	pl.Labels = collections.MergeMaps(info.eksConnection.Labels, pl.Labels)
}

func getFrpsPrivatelink(info ProcedureInfo, frpsSvcName, frpsSvcNs string) *eksv1alpha1.Privatelink {
	pl := &eksv1alpha1.Privatelink{}
	pl.Name = namer.UniqueIdentifier(info.eksConnection.ObjectMeta)
	mutateFrpsPrivatelinkSpec(pl, info, frpsSvcName, frpsSvcNs)
	return pl
}

// Status retrieves the status for the deploying step.
func (f *frpsPrivatelink) Status(ctx context.Context, baseCli client.Reader, infraCli client.Client, info ProcedureInfo) (bool, error) {
	pl := &eksv1alpha1.Privatelink{}
	err := infraCli.Get(ctx, types.NamespacedName{Name: namer.UniqueIdentifier(info.eksConnection.ObjectMeta)}, pl)
	if err != nil {
		return false, err
	}
	return isPrivatelinkReady(pl), nil
}

// isPrivatelinkReady return true if the privatelink is ready.
func isPrivatelinkReady(pl *eksv1alpha1.Privatelink) bool {
	return pl.Status.Conditions.GetCondition(eksv1alpha1.ConditionReady).Status == corev1.ConditionTrue
}

// Name returns the name of the step.
func (f *frpsPrivatelink) Name() string {
	return frpsPrivatelinkName
}

// NewFrpsPL returns the procedure for frps deployment.
func NewFrpsPL(s *runtime.Scheme) *frpsPrivatelink {
	return &frpsPrivatelink{scheme: s}
}

var _ Procedure = &frpsPrivatelink{}
