// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package baseserver_test

import (
	"context"
	"fmt"
	"net/http"
	"testing"

	"github.com/gitpod-io/gitpod/common-go/baseserver"
	"github.com/prometheus/client_golang/prometheus/testutil"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/health/grpc_health_v1"
)

func TestServer_StartStop(t *testing.T) {
	// We don't use the helper NewForTests, because we want to control stopping ourselves.
	srv, err := baseserver.New("server_test",
		baseserver.WithUnderTest(),
		baseserver.WithHTTP(&baseserver.ServerConfiguration{Address: "localhost:8765"}),
		baseserver.WithGRPC(&baseserver.ServerConfiguration{Address: "localhost:8766"}),
	)
	require.NoError(t, err)
	baseserver.StartServerForTests(t, srv)

	require.Equal(t, "http://127.0.0.1:8765", srv.HTTPAddress())
	require.Equal(t, "localhost:8766", srv.GRPCAddress())
	require.NoError(t, srv.Close())
}

func TestServer_ServerCombinations_StartsAndStops(t *testing.T) {
	tests := []struct {
		StartHTTP bool
		StartGRPC bool
	}{
		{StartHTTP: false, StartGRPC: false},
		{StartHTTP: true, StartGRPC: false},
		{StartHTTP: true, StartGRPC: true},
		{StartHTTP: false, StartGRPC: true},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("with http: %v, grpc: %v", test.StartGRPC, test.StartHTTP), func(t *testing.T) {
			var opts []baseserver.Option
			opts = append(opts, baseserver.WithUnderTest())
			if test.StartHTTP {
				opts = append(opts, baseserver.WithHTTP(&baseserver.ServerConfiguration{
					Address: fmt.Sprintf("localhost:%d", baseserver.MustFindFreePort(t)),
				}))
			}

			if test.StartGRPC {
				opts = append(opts, baseserver.WithGRPC(&baseserver.ServerConfiguration{
					Address: fmt.Sprintf("localhost:%d", baseserver.MustFindFreePort(t)),
				}))
			}

			srv, err := baseserver.New("test_server", opts...)
			if err != nil {
				t.Fatal(err)
			}
			baseserver.StartServerForTests(t, srv)

			require.NotEmpty(t, srv.DebugAddress(), "must serve debug endpoint")
			if test.StartHTTP {
				require.NotEmpty(t, srv.HTTPAddress(), "must serve http because startHTTP was set")
			} else {
				require.Empty(t, srv.HTTPAddress(), "must not serve http")
			}

			if test.StartGRPC {
				require.NotEmpty(t, srv.GRPCAddress(), "must serve grpc because startGRPC was set")
			} else {
				require.Empty(t, srv.GRPCAddress(), "must not serve grpc")
			}
		})
	}
}

func TestServer_Metrics_gRPC(t *testing.T) {
	ctx := context.Background()
	srv := baseserver.NewForTests(t, baseserver.WithGRPC(&baseserver.ServerConfiguration{
		Address: fmt.Sprintf("localhost:%d", baseserver.MustFindFreePort(t)),
	}))

	// At this point, there must be metrics registry available for use
	require.NotNil(t, srv.MetricsRegistry())
	// Let's start our server up
	baseserver.StartServerForTests(t, srv)

	// We need a client to be able to invoke the RPC, let's construct one
	conn, err := grpc.DialContext(ctx, srv.GRPCAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
	require.NoError(t, err)
	client := grpc_health_v1.NewHealthClient(conn)

	// By default the server runs a Health service, we can use it for our purposes
	_, err = client.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
	require.NoError(t, err)

	// Finally, we can assert that some metrics were produced.
	registry := srv.MetricsRegistry()
	// We expect at least the following. It's not the full set, but a good baseline to sanity check.
	expected := []string{"grpc_server_handled_total", "grpc_server_handling_seconds", "grpc_server_started_total"}

	count, err := testutil.GatherAndCount(registry, expected...)
	require.NoError(t, err)
	require.Equal(t, len(expected)*1, count, "expected 1 count for each metric")
}

func TestServer_Metrics_HTTP(t *testing.T) {
	srv := baseserver.NewForTests(t, baseserver.WithHTTP(&baseserver.ServerConfiguration{
		Address: fmt.Sprintf("localhost:%d", baseserver.MustFindFreePort(t)),
	}))

	// At this point, there must be metrics registry available for use
	require.NotNil(t, srv.MetricsRegistry())
	// Let's start our server up
	baseserver.StartServerForTests(t, srv)

	_, err := http.Get(fmt.Sprintf("%s/foo/bar", srv.HTTPAddress()))
	require.NoError(t, err)

	registry := srv.MetricsRegistry()

	count, err := testutil.GatherAndCount(registry, "gitpod_http_request_duration_seconds", "gitpod_http_response_size_bytes", "gitpod_http_requests_inflight")
	require.NoError(t, err)
	require.Equal(t, 3, count, "expecting 1 count for each above metric")
}
