package gke

import (
	"context"
	"fmt"

	"github.com/samber/lo"
	"google.golang.org/api/compute/v1"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/cloud/gcp"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/controllers/common"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

// LoadBalancer task sets configuration for load balancers, such as connection draining timeout.
type LoadBalancer struct {
	*task.BaseTask
	gcpCli gcp.Client
}

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

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

func (t *LoadBalancer) Create(ctx context.Context) deploy.Result {
	if !t.Cluster.FeatureEnabled(consts.ClusterFeatureEnableGracefulShutdown) {
		return deploy.Done()
	}

	// List all private LB services in the TiDB namespace.
	cli := t.EnvProvider.GetClient()
	var svcList corev1.ServiceList
	if err := cli.List(ctx, &svcList, client.InNamespace(t.Namer.GetTiDBNamespace()), client.MatchingLabels{
		common.Component: common.PrivateLoadbalance,
		common.UsedBy:    common.CustomerTiDB,
	}); err != nil {
		return deploy.Error(fmt.Errorf("list service failed: %w", err))
	}

	// Collect backend service names from services of type LoadBalancer.
	backendServiceNames := make([]string, 0, len(svcList.Items))
	for _, svc := range svcList.Items {
		if svc.Spec.Type == corev1.ServiceTypeLoadBalancer {
			// Get the first non-empty annotation
			name, ok := lo.Coalesce(
				// The annotation exists if ILB subsetting enabled.
				svc.Annotations[consts.AnnoKeyGKESubsettingILBBackendService],
				// use default annoKeyForwardingRuleName is set in FullService task
				svc.Annotations[annoKeyForwardingRuleName],
			)
			if ok {
				backendServiceNames = append(backendServiceNames, name)
				t.Logger.Info("found load balancer service", "name", svc.Name, "namespace", svc.Namespace, "backendServiceName", name)
			}
		}
	}

	// Update backend services with the desired connection draining timeout.
	timeout := t.Config.ClusterController.GetLBConnectionDrainingTimeout(t.Cluster.Spec.Kubernetes.Provider)
	gcpProject, region := t.EnvProvider.GetAccount(), t.EnvProvider.GetRegion()
	for _, name := range backendServiceNames {
		bs, err := t.gcpCli.GetBackendService(ctx, gcpProject, region, name)
		if err != nil {
			return deploy.Error(fmt.Errorf("get backend service failed: %w", err))
		}
		t.Logger.Info("get backend service", "name", bs.Name, "timeout", timeout, "drainingTimeout", bs.ConnectionDraining)
		if bs.ConnectionDraining == nil || bs.ConnectionDraining.DrainingTimeoutSec != timeout {
			if err := t.gcpCli.PatchBackendService(ctx, gcpProject, region, getPatchBackendService(bs, timeout)); err != nil {
				return deploy.Error(fmt.Errorf("patch backend service failed: %w", err))
			}
		}
	}
	return deploy.Done()
}

// getPatchBackendService creates a patch for updating the backend service's connection draining timeout.
func getPatchBackendService(bs *compute.BackendService, timeout int64) *compute.BackendService {
	return &compute.BackendService{
		ConnectionDraining: &compute.ConnectionDraining{
			DrainingTimeoutSec: timeout,
		},
		Fingerprint: bs.Fingerprint,
		Name:        bs.Name,
	}
}

func (t *LoadBalancer) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *LoadBalancer) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *LoadBalancer) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
