package server

import (
	"context"
	"net"
	"sync"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/kas2agentk_tunnel"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modserver"
	"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/tunclient"
	"google.golang.org/grpc"
)

const (
	getAgentsInitBackoff   = 10 * time.Second
	getAgentsMaxBackoff    = 10 * time.Minute
	getAgentsResetDuration = 15 * time.Minute
	getAgentsBackoffFactor = 2.0
	getAgentsJitter        = 1.0

	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 {
	AgentConnPool        AgentConnPool
	AgentInMemServer     *grpc.Server
	AgentInMemListener   net.Listener
	AgentInMemServerConn *grpc.ClientConn
	AgentInMemAuxCancel  context.CancelFunc
}

func (f *Factory) New(config *modserver.Config) (modserver.Module, error) {
	cfg := config.Config.Agent.ReceptiveAgent
	return &module{
		log:          config.Log,
		api:          config.API,
		gitLabClient: config.GitLabClient,
		getAgentsPollConfig: retry.NewPollConfigFactory(
			cfg.PollPeriod.AsDuration(),
			retry.NewExponentialBackoffFactory(
				getAgentsInitBackoff,
				getAgentsMaxBackoff,
				getAgentsResetDuration,
				getAgentsBackoffFactor,
				getAgentsJitter,
			),
		),
		agentInMemServer:     f.AgentInMemServer,
		agentInMemListener:   f.AgentInMemListener,
		agentInMemServerConn: f.AgentInMemServerConn,
		agentInMemAuxCancel:  f.AgentInMemAuxCancel,
		wf: &connWorkerFactory{
			log:                config.Log,
			api:                config.API,
			minIdleConnections: minIdleConnections,
			maxConnections:     maxConnections,
			scaleUpStep:        scaleUpStep,
			maxIdleTime:        maxIdleTime,
			userAgent:          config.KASNameVersion,
			jwtAudience:        api.JWTAgentk,
			jwtIssuer:          api.JWTKAS,
			connectionPollConfig: retry.NewPollConfigFactory(0, retry.NewExponentialBackoffFactory(
				connectionInitBackoff,
				connectionMaxBackoff,
				connectionResetDuration,
				connectionBackoffFactor,
				connectionJitter,
			)),
			// Lazily (and only once) construct APIDescriptor once all RPC handlers have been registered.
			apiDescriptor: sync.OnceValue(func() *info.APIDescriptor {
				return tunclient.APIDescriptor(config.AgentServer)
			}),
			agentConnPool:     f.AgentConnPool,
			agentServerConn:   f.AgentInMemServerConn,
			tp:                config.TraceProvider,
			mp:                config.MeterProvider,
			p:                 config.TracePropagator,
			streamClientProm:  config.StreamClientProm,
			unaryClientProm:   config.UnaryClientProm,
			validator:         config.Validator,
			grpcClientTracing: config.GRPCClientTracing,
		},
	}, nil
}

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