package test

import (
	"context"
	"net"
	"testing"

	"buf.build/go/protovalidate"
	"github.com/ash2k/stager"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	agent2kas_tunnel_agent "gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent2kas_tunnel/agent"
	agent2kas_tunnel_agentk "gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent2kas_tunnel/agentk"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modagentk"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/grpctool"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/mock_modagent"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/tool/grpcz"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func agentConstructComponents(ctx context.Context, t *testing.T, kasConn grpc.ClientConnInterface, agentAPI *mock_modagent.MockAPI) (func(context.Context) error, *grpc.Server) {
	log := testlogger.New(t)
	v, err := protovalidate.New()
	require.NoError(t, err)
	apiServerListener := grpcz.NewPipeDialListener()
	apiServer := agentConstructAPIServer(ctx, v)
	apiServerConn, err := agentConstructInternalServerConn(apiServerListener.DialContext)
	require.NoError(t, err)
	t.Cleanup(func() {
		assert.NoError(t, apiServerConn.Close())
	})

	f := agent2kas_tunnel_agentk.Factory{
		Factory: agent2kas_tunnel_agent.Factory{
			APIServerConn: apiServerConn,
		},
	}
	config := &modagentk.Config{
		Config: modagent.Config{
			Log:       log,
			API:       agentAPI,
			KASConn:   kasConn,
			APIServer: apiServer,
			Validator: v,
		},
	}
	m, err := f.New(config)
	require.NoError(t, err)
	return func(ctx context.Context) error {
		return stager.RunStages(ctx,
			// Start modules.
			func(stage stager.Stage) {
				stage.Go(func(ctx context.Context) error {
					return m.Run(ctx, nil)
				})
			},
			func(stage stager.Stage) {
				// Start API gRPC server.
				agentStartAPIServer(stage, apiServer, apiServerListener)
			},
		)
	}, apiServer
}

func agentConstructAPIServer(ctx context.Context, v protovalidate.Validator) *grpc.Server {
	return grpc.NewServer(
		grpc.StatsHandler(grpcz.NewServerMaxConnAgeStatsHandler(ctx, 0)),
		grpc.ChainStreamInterceptor(
			grpctool.StreamServerValidatingInterceptor(v),
		),
		grpc.ChainUnaryInterceptor(
			grpctool.UnaryServerValidatingInterceptor(v),
		),
	)
}

func agentStartAPIServer(stage stager.Stage, apiServer *grpc.Server, apiServerListener net.Listener) {
	grpctool.StartServer(stage, apiServer,
		func(ctx context.Context) (net.Listener, error) {
			return apiServerListener, nil
		},
		func() {},
		func() {},
	)
}

func agentConstructInternalServerConn(dialContext func(ctx context.Context, addr string) (net.Conn, error)) (*grpc.ClientConn, error) {
	return grpc.NewClient("passthrough:internal-server",
		grpc.WithContextDialer(dialContext),
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
}
