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/gitlab-agent/v18/internal/tool/retry"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tunnel/info"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tunnel/rpc"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/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)
	pollConfig := retry.NewPollConfigFactory(0, retry.NewExponentialBackoffFactory(
		connectionInitBackoff,
		connectionMaxBackoff,
		connectionResetDuration,
		connectionBackoffFactor,
		connectionJitter,
	))
	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:    pollConfig,
				OnActive:      onActive,
				OnIdle:        onIdle,
				PrepareMetadata: func(md metadata.MD) metadata.MD {
					return md
				},
			}
		},
	)
}
