package agent

import (
	"log/slog"
	"sync"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent2kas_tunnel"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/grpctool"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/info"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/rpc"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/tool/retry"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/tunclient"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

const (
	minIdleConnections = 2
	maxConnections     = 100 * 1024
	maxIdleTime        = time.Minute
	// scaleUpStep defines how many new connections are started when there is not enough idle connections.
	scaleUpStep = 10

	connectionInitBackoff   = 1 * time.Second
	connectionMaxBackoff    = 20 * time.Second
	connectionResetDuration = 25 * time.Second
	connectionBackoffFactor = 1.6
	connectionJitter        = 5.0
)

type Factory struct {
	APIServerConn grpc.ClientConnInterface
}

func (f *Factory) IsProducingLeaderModules() bool {
	return false
}

func (f *Factory) Name() string {
	return agent2kas_tunnel.ModuleName
}

func (f *Factory) GetConnectionManager(log *slog.Logger, kasConn grpc.ClientConnInterface, apiServer grpctool.GRPCServer) *tunclient.ConnectionManager {
	rtClient := rpc.NewReverseTunnelClient(kasConn)
	descriptor := sync.OnceValue(func() *info.APIDescriptor {
		return tunclient.APIDescriptor(apiServer)
	})
	return tunclient.NewConnectionManager(
		minIdleConnections,
		maxConnections,
		scaleUpStep,
		maxIdleTime,
		func(onActive, onIdle func(c tunclient.ConnectionInterface)) tunclient.ConnectionInterface {
			return &tunclient.Connection{
				Log:           log,
				Descriptor:    descriptor(),
				Client:        rtClient,
				OwnServerConn: f.APIServerConn,
				PollConfig: retry.PollConfig{
					Interval: 0,
					Backoff: retry.NewExponentialBackoff(
						connectionInitBackoff,
						connectionMaxBackoff,
						connectionResetDuration,
						connectionBackoffFactor,
						connectionJitter,
					),
				},
				OnActive: onActive,
				OnIdle:   onIdle,
				PrepareMetadata: func(md metadata.MD) metadata.MD {
					return md
				},
			}
		},
	)
}
