package common

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservice/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"k8s.io/apimachinery/pkg/types"
)

type LoadBalancer struct {
	*task.BaseTask
}

func NewLoadBalancer(ctx *task.Context) *LoadBalancer {
	return &LoadBalancer{&task.BaseTask{Context: ctx}}
}

func (l *LoadBalancer) Name() string {
	return "LoadBalancer"
}

func (l *LoadBalancer) Create(ctx context.Context) deploy.Result {
	l.BeginAt = util.Pointer(time.Now())
	return l.updateLBStatus(ctx)
}

// retrieve private lb info from cs and update the lb status
func (l *LoadBalancer) updateLBStatus(ctx context.Context) deploy.Result {
	targetLb := l.PrivateLinkService.Spec.LoadBalancerName
	lbObj := new(v1alpha1.LoadBalancer)
	if err := l.Local.Get(ctx, types.NamespacedName{Name: targetLb,
		Namespace: l.PrivateLinkService.Namespace}, lbObj); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	ready := true
	if lbObj.Status.CloudResourceId == "" {
		l.Logger.Info("loadbalancer is not ready", "name", targetLb)
		ready = false
	}
	l.PrivateLinkService.Status.DNSIdentify = lbObj.Spec.Identity
	l.PrivateLinkService.Status.DNSSubIdentify = lbObj.Spec.SubIdentity
	primaryPort, listenerPorts := primaryAndListenerPorts(lbObj)
	l.PrivateLinkService.Status.LoadBalancer = v1alpha1.LoadBalancerInfo{
		Port:            primaryPort,
		Ports:           listenerPorts,
		CloudResourceId: lbObj.Status.CloudResourceId,
	}
	if err := l.PrivateLinkServiceRW.UpdateStatus(ctx, l.PrivateLinkService); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	if !ready {
		return deploy.Panic(fmt.Errorf("loadbalancer is not ready")).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

// primaryAndListenerPorts derives the legacy single-port value and the full
// multi-port list from a LoadBalancer Spec. Order:
//   - Spec.Ports non-empty: Ports = every entry's Port; primary = Ports[0].
//   - Spec.Ports empty: Ports = nil; primary = Spec.Port.
func primaryAndListenerPorts(lbObj *v1alpha1.LoadBalancer) (int32, []int32) {
	if len(lbObj.Spec.Ports) == 0 {
		return lbObj.Spec.Port, nil
	}
	listenerPorts := make([]int32, 0, len(lbObj.Spec.Ports))
	for _, p := range lbObj.Spec.Ports {
		if p.Port == 0 {
			continue
		}
		listenerPorts = append(listenerPorts, p.Port)
	}
	if len(listenerPorts) == 0 {
		return lbObj.Spec.Port, nil
	}
	return listenerPorts[0], listenerPorts
}
