package grpctool

import (
	"context"
	"testing"

	"buf.build/go/protovalidate"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/grpctool/test"
	"gitlab.com/gitlab-org/cluster-integration/tunnel/tool/grpcz"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"
)

func TestValidator(t *testing.T) {
	lis := grpcz.NewPipeDialListener()
	defer lis.Close()

	v, err := protovalidate.New()
	require.NoError(t, err)

	server := grpc.NewServer(
		grpc.ChainStreamInterceptor(StreamServerValidatingInterceptor(v)),
		grpc.ChainUnaryInterceptor(UnaryServerValidatingInterceptor(v)),
	)
	defer server.GracefulStop()
	test.RegisterTestingServer(server, &test.GRPCTestingServer{
		UnaryFunc: func(ctx context.Context, request *test.Request) (*test.Response, error) {
			return &test.Response{
				// invalid response because Message is not set
			}, nil
		},
		StreamingFunc: func(server grpc.BidiStreamingServer[test.Request, test.Response]) error {
			_, recvErr := server.Recv()
			if recvErr != nil {
				return recvErr
			}
			return server.Send(&test.Response{
				// invalid response because Message is not set
			})
		},
	})
	go func() {
		assert.NoError(t, server.Serve(lis))
	}()

	conn, err := grpc.NewClient("passthrough:pipe",
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithChainStreamInterceptor(StreamClientValidatingInterceptor(v)),
		grpc.WithChainUnaryInterceptor(UnaryClientValidatingInterceptor(v)),
		grpc.WithContextDialer(lis.DialContext),
	)
	require.NoError(t, err)
	defer conn.Close()
	client := test.NewTestingClient(conn)
	t.Run("invalid unary response", func(t *testing.T) {
		_, errr := client.RequestResponse(context.Background(), &test.Request{S1: "valid"})
		assert.EqualError(t, errr, "rpc error: code = InvalidArgument desc = invalid server response: validation error: message: exactly one field is required in oneof")
		assertHasDetails(t, errr)
	})
	t.Run("invalid streaming response", func(t *testing.T) {
		ctx := t.Context()
		stream, errr := client.StreamingRequestResponse(ctx)
		require.NoError(t, errr)
		errr = stream.Send(&test.Request{S1: "valid"})
		require.NoError(t, errr)
		_, errr = stream.Recv()
		assert.EqualError(t, errr, "rpc error: code = InvalidArgument desc = invalid server response: validation error: message: exactly one field is required in oneof")
		assertHasDetails(t, errr)
	})
	t.Run("invalid unary request", func(t *testing.T) {
		_, errr := client.RequestResponse(context.Background(), &test.Request{})
		assert.EqualError(t, errr, "rpc error: code = InvalidArgument desc = invalid client request: validation error: s1: value length must be at least 1 bytes")
		assertHasDetails(t, errr)
	})
	t.Run("invalid streaming request", func(t *testing.T) {
		ctx := t.Context()
		stream, errr := client.StreamingRequestResponse(ctx)
		require.NoError(t, errr)
		errr = stream.Send(&test.Request{})
		require.NoError(t, errr)
		_, errr = stream.Recv()
		assert.EqualError(t, errr, "rpc error: code = InvalidArgument desc = invalid client request: validation error: s1: value length must be at least 1 bytes")
		assertHasDetails(t, errr)
	})
}

func assertHasDetails(t *testing.T, err error) {
	s, ok := status.FromError(err)
	require.True(t, ok)
	assert.Len(t, s.Details(), 1)
}
