package kas

import (
	"context"
	"encoding/json"
	"fmt"
	"runtime"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent_tracker"
	"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/testhelpers"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/pkg/entity"
	"go.uber.org/mock/gomock"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// Test for https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/issues/374.
// Test for https://github.com/open-telemetry/opentelemetry-go/issues/3769.
// If it fails, make sure you've updated the version of go.opentelemetry.io/otel/semconv that's imported to match
// what OTEL SDK uses internally.
func TestConstructResource(t *testing.T) {
	_, err := constructOTELResource()
	require.NoError(t, err)
}

func TestRedisAgentInfoResolver_Agentk(t *testing.T) {
	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentkConnectionsByID(gomock.Any(), testhelpers.AgentkKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentkInfo) bool) {
			yield(&agent_tracker.ConnectedAgentkInfo{
				AgentMeta: &entity.AgentkMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					PodNamespace: "agentk-pod-namespace",
					PodName:      "agentk-pod-name",
					KubernetesVersion: &entity.KubernetesVersion{
						Major:      "1",
						Minor:      "2",
						GitVersion: "3",
						Platform:   "4",
					},
				},
				ConnectedAt:  timestamppb.New(time.Unix(42, 24)),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentkKey1.ID,
				ProjectId:    testhelpers.ProjectID,
			})
		})
	r := redisAgentInfoResolver{
		agentQuerier: m,
	}
	data, err := r.Get(context.Background(), testhelpers.AgentkKey1)
	require.NoError(t, err)

	b, err := json.Marshal(data)
	require.NoError(t, err)

	assert.JSONEq(t, `{"agents":[{"agent_meta":{"version":"v1.0.0","pod_namespace":"agentk-pod-namespace","pod_name":"agentk-pod-name","kubernetes_version":{"major":"1","minor":"2","git_version":"3","platform":"4"},"git_ref":"0123456789abcdef0123456789abcdef00000000"},"connected_at":"1970-01-01T00:00:42.000000024Z","connection_id":"234234","agent_id":"123","project_id":"321"}]}`, string(b))
}

func TestRedisAgentInfoResolver_Agentw(t *testing.T) {
	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentwConnectionsByID(gomock.Any(), testhelpers.AgentwKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentwInfo) bool) {
			yield(&agent_tracker.ConnectedAgentwInfo{
				AgentMeta: &entity.AgentwMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					Architecture: runtime.GOARCH,
				},
				ConnectedAt:  timestamppb.New(time.Unix(42, 24)),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentwKey1.ID,
			})
		})
	r := redisAgentInfoResolver{
		agentQuerier: m,
	}
	data, err := r.Get(context.Background(), testhelpers.AgentwKey1)
	require.NoError(t, err)

	b, err := json.Marshal(data)
	require.NoError(t, err)

	assert.JSONEq(t, fmt.Sprintf(`{"agents":[{"agent_meta":{"version":"v1.0.0","architecture":"%s","git_ref":"0123456789abcdef0123456789abcdef00000000"},"connected_at":"1970-01-01T00:00:42.000000024Z","connection_id":"234234","agent_id":"1123"}]}`, runtime.GOARCH), string(b))
}

func TestAgentFinder_Agentk_AgentLastConnected_NoAgents(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentkConnectionsByID(gomock.Any(), testhelpers.AgentkKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentkInfo) bool) {})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentkKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, time.Time{}, actualLastConnectedAt)
}

func TestAgentFinder_Agentw_AgentLastConnected_NoAgents(t *testing.T) {
	// GIVEN
	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentwConnectionsByID(gomock.Any(), testhelpers.AgentwKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentwInfo) bool) {})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentwKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, time.Time{}, actualLastConnectedAt)
}

func TestAgentFinder_Agentk_AgentLastConnected(t *testing.T) {
	// GIVEN
	expectedLastConnectedAt := time.Now().UTC()

	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentkConnectionsByID(gomock.Any(), testhelpers.AgentkKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentkInfo) bool) {
			yield(&agent_tracker.ConnectedAgentkInfo{
				AgentMeta: &entity.AgentkMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					PodNamespace: "agentk-pod-namespace",
					PodName:      "agentk-pod-name",
					KubernetesVersion: &entity.KubernetesVersion{
						Major:      "1",
						Minor:      "2",
						GitVersion: "3",
						Platform:   "4",
					},
				},
				ConnectedAt:  timestamppb.New(expectedLastConnectedAt),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentkKey1.ID,
				ProjectId:    testhelpers.ProjectID,
			})
		})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentkKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, expectedLastConnectedAt, actualLastConnectedAt)
}

func TestAgentFinder_Agentw_AgentLastConnected(t *testing.T) {
	// GIVEN
	expectedLastConnectedAt := time.Now().UTC()

	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentwConnectionsByID(gomock.Any(), testhelpers.AgentwKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentwInfo) bool) {
			yield(&agent_tracker.ConnectedAgentwInfo{
				AgentMeta: &entity.AgentwMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					Architecture: runtime.GOARCH,
				},
				ConnectedAt:  timestamppb.New(expectedLastConnectedAt),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentwKey1.ID,
			})
		})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentwKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, expectedLastConnectedAt, actualLastConnectedAt)
}

func TestAgentFinder_Agentk_AgentLastConnected_MultipleReplicas(t *testing.T) {
	// GIVEN
	pod1ConnectedAt := time.Now().UTC().Add(-1 * time.Minute)
	pod2ConnectedAt := time.Now().UTC().Add(+1 * time.Minute)
	expectedLastConnectedAt := pod2ConnectedAt

	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentkConnectionsByID(gomock.Any(), testhelpers.AgentkKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentkInfo) bool) {
			if !yield(&agent_tracker.ConnectedAgentkInfo{
				AgentMeta: &entity.AgentkMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					PodNamespace: "agentk-pod-namespace",
					PodName:      "agentk-pod-name-1",
					KubernetesVersion: &entity.KubernetesVersion{
						Major:      "1",
						Minor:      "2",
						GitVersion: "3",
						Platform:   "4",
					},
				},
				ConnectedAt:  timestamppb.New(pod1ConnectedAt),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentkKey1.ID,
				ProjectId:    testhelpers.ProjectID,
			}) {
				return
			}
			yield(&agent_tracker.ConnectedAgentkInfo{
				AgentMeta: &entity.AgentkMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					PodNamespace: "agentk-pod-namespace",
					PodName:      "agentk-pod-name-2",
					KubernetesVersion: &entity.KubernetesVersion{
						Major:      "1",
						Minor:      "2",
						GitVersion: "3",
						Platform:   "4",
					},
				},
				ConnectedAt:  timestamppb.New(pod2ConnectedAt),
				ConnectionId: 234235,
				AgentId:      testhelpers.AgentkKey1.ID,
				ProjectId:    testhelpers.ProjectID,
			})
		})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentkKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, expectedLastConnectedAt, actualLastConnectedAt)
}

func TestAgentFinder_Agentw_AgentLastConnected_MultipleReplicas(t *testing.T) {
	// GIVEN
	pod1ConnectedAt := time.Now().UTC().Add(-1 * time.Minute)
	pod2ConnectedAt := time.Now().UTC().Add(+1 * time.Minute)
	expectedLastConnectedAt := pod2ConnectedAt

	ctrl := gomock.NewController(t)
	m := mock_agent_tracker.NewMockTracker(ctrl)
	m.EXPECT().
		GetAgentwConnectionsByID(gomock.Any(), testhelpers.AgentwKey1.ID).
		Return(func(yield func(*agent_tracker.ConnectedAgentwInfo) bool) {
			if !yield(&agent_tracker.ConnectedAgentwInfo{
				AgentMeta: &entity.AgentwMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					Architecture: runtime.GOARCH,
				},
				ConnectedAt:  timestamppb.New(pod1ConnectedAt),
				ConnectionId: 234234,
				AgentId:      testhelpers.AgentwKey1.ID,
			}) {
				return
			}
			yield(&agent_tracker.ConnectedAgentwInfo{
				AgentMeta: &entity.AgentwMeta{
					Version:      "v1.0.0",
					GitRef:       "0123456789abcdef0123456789abcdef00000000",
					Architecture: runtime.GOARCH,
				},
				ConnectedAt:  timestamppb.New(pod2ConnectedAt),
				ConnectionId: 234235,
				AgentId:      testhelpers.AgentwKey1.ID,
			})
		})

	// WHEN
	af := agentFinder{
		agentQuerier: m,
	}
	actualLastConnectedAt, err := af.AgentLastConnected(context.Background(), testhelpers.AgentwKey1)

	// THEN
	require.NoError(t, err)
	assert.Equal(t, expectedLastConnectedAt, actualLastConnectedAt)
}
