package procedure

import (
	"context"

	corev1 "k8s.io/api/core/v1"
	"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"

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

const (
	frpServerName = "frps-server"
	frpsImageKey  = "frpsImage"

	frpsBindPort     = 8080
	eksAPIServerPort = 443
	clusterProxyPort = 80
)

type frpServer struct {
	scheme *runtime.Scheme
}

// Deploy deploys resource for the specified tenant.
func (f *frpServer) Deploy(ctx context.Context, baseCli, infraCli client.Client, info ProcedureInfo) error {
	baseReader := AsReader(baseCli)
	frpsImage, err := GetConfigWithKey(ctx, baseReader, info, frpsImageKey)
	if err != nil {
		return err
	}
	frps := getFrpServer(info, frpsImage)
	_, err = controllerutil.CreateOrUpdate(ctx, infraCli, frps, func() error {
		mutateFrpsSpec(frps, info, frpsImage)
		err := controllerutil.SetControllerReference(info.eksConnection, frps, f.scheme)
		if IsAlreadyOwnedError(err) {
			return nil
		}
		return err
	})
	return err
}

func mutateFrpsSpec(frps *eksv1alpha1.Frpserver, info ProcedureInfo, frpsImage string) {
	// TODO: do we need to update the frps image which could cause potential disturbance?
	frps.Spec.FrpsImage = frpsImage
	frps.Spec.BindPort = frpsBindPort
	frps.Spec.TcpPorts = []int{
		eksAPIServerPort,
	}
	frps.Spec.ExternalTcpPorts = []int{
		clusterProxyPort,
	}
	frps.Labels = collections.MergeMaps(info.eksConnection.Labels, frps.Labels)
}

func getFrpServer(info ProcedureInfo, frpsImage string) *eksv1alpha1.Frpserver {
	frps := &eksv1alpha1.Frpserver{}
	frps.Name = namer.UniqueIdentifier(info.eksConnection.ObjectMeta)
	mutateFrpsSpec(frps, info, frpsImage)
	return frps
}

// Status retrieves the status for the deploying step.
func (f *frpServer) Status(ctx context.Context, baseCli client.Reader, infraCli client.Client, info ProcedureInfo) (bool, error) {
	frps := &eksv1alpha1.Frpserver{}
	err := infraCli.Get(ctx, types.NamespacedName{Name: namer.UniqueIdentifier(info.eksConnection.ObjectMeta)}, frps)
	if err != nil {
		return false, err
	}
	if !isFrpsReady(frps) {
		return false, nil
	}
	if svcDomainName := util.GetAnnotation(frps, AnnotationKeyFrpsExternalSvcName); svcDomainName != "" {
		info.eksConnection.Status.ProxyBaseURL = svcDomainName
	}
	return true, nil
}

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

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

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

var _ Procedure = &frpServer{}
