package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modserver"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/matcher"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/mock_modserver"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testhelpers"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testlogger"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

func TestAgentRPCAPIFactory_HappyPath(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	info := testhelpers.AgentkInfoObj()
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(info, nil)
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	md = prepareMetadata(testhelpers.AgentkKey1, md)
	ctx := metadata.NewIncomingContext(context.Background(), md)
	rpcAPI, err := f(ctx, "/service/method")
	require.NoError(t, err)

	gotInfo, err := rpcAPI.AgentInfo(context.Background(), testlogger.New(t))
	require.NoError(t, err)
	matcher.AssertProtoEqual(t, info, gotInfo)
}

func TestAgentRPCAPIFactory_DelegateError(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(nil, status.Error(codes.Unauthenticated, "you shall not pass!"))
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	md = prepareMetadata(testhelpers.AgentkKey1, md)
	ctx := metadata.NewIncomingContext(context.Background(), md)
	rpcAPI, err := f(ctx, "/service/method")
	require.NoError(t, err)

	_, err = rpcAPI.AgentInfo(context.Background(), testlogger.New(t))
	assert.EqualError(t, err, "rpc error: code = Unauthenticated desc = you shall not pass!")
}

func TestAgentRPCAPIFactory_MissingAgentIDMDKey(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	mockRPCAPI.EXPECT().
		Log().
		Return(testlogger.New(t)).
		MinTimes(1)
	mockRPCAPI.EXPECT().
		HandleProcessingError(gomock.Any(), "AgentKey validation", matcher.ErrorEq("expecting a single kas2agentk-tunnel-agent-id, got 0"))
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	ctx := metadata.NewIncomingContext(context.Background(), md)
	_, err := f(ctx, "/service/method")
	assert.EqualError(t, err, "rpc error: code = Internal desc = expecting a single kas2agentk-tunnel-agent-id, got 0")
}

func TestAgentRPCAPIFactory_ParsingError(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{agentIDMDKey: []string{"not an integer"}}
	ctx := metadata.NewIncomingContext(context.Background(), md)
	_, err := f(ctx, "/service/method")
	assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = invalid kas2agentk-tunnel-agent-id")
}

func TestAgentIDMDKeyIsLowercase_Set(t *testing.T) {
	md := metadata.MD{}
	md.Set(agentIDMDKey, "abc")
	assert.Contains(t, md, agentIDMDKey)
}

func TestAgentIDMDKeyIsLowercase_Get(t *testing.T) {
	md := metadata.MD{
		agentIDMDKey: []string{"abc"},
	}
	got := md.Get(agentIDMDKey)
	assert.Equal(t, []string{"abc"}, got)
}

func TestAgentRPCAPIFactory_MissingAgentTypeMDKey(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	info := testhelpers.AgentkInfoObj()
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(info, nil)
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	md = prepareMetadata(testhelpers.AgentkKey1, md)
	delete(md, agentTypeMDKey)
	ctx := metadata.NewIncomingContext(context.Background(), md)
	rpcAPI, err := f(ctx, "/service/method")
	require.NoError(t, err)

	gotInfo, err := rpcAPI.AgentInfo(context.Background(), testlogger.New(t))
	require.NoError(t, err)
	matcher.AssertProtoEqual(t, info, gotInfo)
	// assert the agent type should be agentk if the type is missing
	assert.Equal(t, testhelpers.AgentkKey1.Type, gotInfo.GetAgentKey().Type)
}

func TestAgentRPCAPIFactory_ExtraAgentTypeMDKey(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	mockRPCAPI.EXPECT().
		Log().
		Return(testlogger.New(t)).
		MinTimes(1)
	mockRPCAPI.EXPECT().
		HandleProcessingError(gomock.Any(), "AgentKey validation", matcher.ErrorEq("expecting a single kas2agentk-tunnel-agent-type, got 2"))
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	md = prepareMetadata(testhelpers.AgentkKey1, md)
	md[agentTypeMDKey] = append(md[agentTypeMDKey], testhelpers.AgentkKey1.Type.String())
	ctx := metadata.NewIncomingContext(context.Background(), md)
	_, err := f(ctx, "/service/method")
	assert.EqualError(t, err, "rpc error: code = Internal desc = expecting a single kas2agentk-tunnel-agent-type, got 2")
}

func TestAgentRPCAPIFactory_UnknownAgentTypeMDKey(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	f := AgentRPCAPIFactory(func(ctx context.Context, fullMethodName string) (modserver.AgentRPCAPI, error) {
		return mockRPCAPI, nil
	})
	md := metadata.MD{"foo": []string{"bar"}}
	md = prepareMetadata(testhelpers.AgentkKey1, md)
	md[agentTypeMDKey] = []string{"agentrandom"}
	ctx := metadata.NewIncomingContext(context.Background(), md)
	_, err := f(ctx, "/service/method")
	assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = unrecognized agent type: agentrandom")
}
