package server

import (
	"context"
	"errors"
	"runtime"
	"testing"

	"github.com/stretchr/testify/assert"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent_registrar/runnerc_rpc"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent_tracker"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modserver"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/mock_agent_tracker"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/mock_event_tracker"
	"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/status"
)

func TestRegister_RunnerController(t *testing.T) {
	mockRPCAPI, mockAgentTracker, mockEventTracker, s, ctx := setupRunnerControllerServer(t)
	req := runnercRegisterRequest()

	mockRPCAPI.EXPECT().
		Log().
		Return(testlogger.New(t))
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(testhelpers.RunnerControllerInfoObj(), nil)
	mockAgentTracker.EXPECT().
		RegisterRunnerControllerExpiring(gomock.Any(), gomock.Any()).
		Do(func(ctx context.Context, connectedAgentInfo *agent_tracker.ConnectedRunnerControllerInfo) error {
			assert.Equal(t, testhelpers.RunnerControllerKey1.ID, connectedAgentInfo.AgentId)
			assert.EqualValues(t, 123456789, connectedAgentInfo.ConnectionId)
			return nil
		})
	expectedEvent := &RegisterRunnerControllerEvent{
		AgentVersion:       "v1.2.3",
		ConnectionID:       123456789,
		AgentID:            testhelpers.RunnerControllerKey1.ID,
		ExtraTelemetryData: map[string]string{"some-key": "some-value"},
		Architecture:       runtime.GOARCH,
	}
	mockEventTracker.EXPECT().EmitEvent(expectedEvent)

	resp, err := s.Register(ctx, req)
	assert.NotNil(t, resp)
	assert.NoError(t, err)
}

func TestRegister_RunnerController_AgentInfo_Error(t *testing.T) {
	mockRPCAPI, _, _, s, ctx := setupRunnerControllerServer(t)
	req := runnercRegisterRequest()

	mockRPCAPI.EXPECT().
		Log().
		Return(testlogger.New(t))
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(nil, status.Error(codes.Unavailable, "failed to register agent"))

	resp, err := s.Register(ctx, req)
	assert.Nil(t, resp)
	assert.Equal(t, codes.Unavailable, status.Code(err))
}

func TestRegister_RunnerController_registerAgent_Error(t *testing.T) {
	mockRPCAPI, mockAgentTracker, _, s, ctx := setupRunnerControllerServer(t)
	req := runnercRegisterRequest()

	expectedErr := errors.New("expected error")

	mockRPCAPI.EXPECT().
		Log().
		Return(testlogger.New(t)).
		AnyTimes()
	mockRPCAPI.EXPECT().
		AgentInfo(gomock.Any(), gomock.Any()).
		Return(testhelpers.RunnerControllerInfoObj(), nil)
	mockAgentTracker.EXPECT().
		RegisterRunnerControllerExpiring(gomock.Any(), gomock.Any()).
		Return(expectedErr)
	mockRPCAPI.EXPECT().
		HandleProcessingError(gomock.Any(), gomock.Any(), expectedErr, logz.AgentKey(testhelpers.RunnerControllerKey1))

	resp, err := s.Register(ctx, req)
	assert.Nil(t, resp)
	assert.Equal(t, codes.Unavailable, status.Code(err))
}

func setupRunnerControllerServer(t *testing.T) (*mock_modserver.MockAgentRPCAPI,
	*mock_agent_tracker.MockTracker, *mock_event_tracker.MockEventsInterface, *runnerControllerServer, context.Context) {
	ctrl := gomock.NewController(t)

	mockRPCAPI := mock_modserver.NewMockAgentRPCAPI(ctrl)
	mockAgentTracker := mock_agent_tracker.NewMockTracker(ctrl)
	mockEventTracker := mock_event_tracker.NewMockEventsInterface(ctrl)

	s := &runnerControllerServer{
		agentRegisterer:           mockAgentTracker,
		registerAgentEventTracker: mockEventTracker,
	}

	ctx := modserver.InjectAgentRPCAPI(context.Background(), mockRPCAPI)

	return mockRPCAPI, mockAgentTracker, mockEventTracker, s, ctx
}

func runnercRegisterRequest() *runnerc_rpc.RegisterRequest {
	return &runnerc_rpc.RegisterRequest{
		Meta:       mock_modserver.RunnerControllerMeta(),
		InstanceId: 123456789,
	}
}
