package server

import (
	"context"
	"log/slog"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent_tracker"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/agent_tracker/rpc"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modserver"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/modshared"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/version"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type server struct {
	rpc.UnsafeAgentTrackerServer
	log                *slog.Logger
	agentQuerier       agent_tracker.Querier
	serverVersion      version.Version
	gitlabReleasesList []string
}

func (s *server) GetConnectedAgentsByAgentIDs(ctx context.Context, req *rpc.GetConnectedAgentksByAgentIDsRequest) (*rpc.GetConnectedAgentksByAgentIDsResponse, error) {
	return s.GetConnectedAgentksByAgentIDs(ctx, req)
}

func (s *server) GetConnectedAgentksByAgentIDs(ctx context.Context, req *rpc.GetConnectedAgentksByAgentIDsRequest) (*rpc.GetConnectedAgentksByAgentIDsResponse, error) {
	var infos []*agent_tracker.ConnectedAgentkInfo
	for _, agentID := range req.AgentIds {
		for info := range s.agentQuerier.GetAgentkConnectionsByID(ctx, agentID) {
			infos = append(infos, info)
		}
	}
	return &rpc.GetConnectedAgentksByAgentIDsResponse{
		Agents: s.constructFromAgentTrackerConnectedAgentInfos(infos),
	}, nil
}

func (s *server) CountAgentsByAgentVersions(ctx context.Context, _ *rpc.CountAgentsByAgentVersionsRequest) (*rpc.CountAgentsByAgentVersionsResponse, error) {
	rpcAPI := modshared.RPCAPIFromContext[modserver.RPCAPI](ctx)
	log := rpcAPI.Log()

	counts, err := s.agentQuerier.CountAgentsByAgentVersions(ctx)
	if err != nil {
		rpcAPI.HandleProcessingError(log, "CountAgentsByAgentVersions() failed", err)
		return nil, status.Error(codes.Unavailable, "CountAgentsByAgentVersions() failed")
	}

	return &rpc.CountAgentsByAgentVersionsResponse{
		AgentVersions: counts,
	}, nil
}

func (s *server) constructFromAgentTrackerConnectedAgentInfos(infos []*agent_tracker.ConnectedAgentkInfo) []*rpc.ConnectedAgentk {
	cas := make([]*rpc.ConnectedAgentk, len(infos))
	for i, info := range infos {
		cas[i] = &rpc.ConnectedAgentk{
			AgentMeta:    info.AgentMeta,
			ConnectedAt:  info.ConnectedAt,
			ConnectionId: info.ConnectionId,
			AgentId:      info.AgentId,
			ProjectId:    info.ProjectId,
			Warnings:     s.getAgentsWarnings(info),
		}
	}
	return cas
}

func (s *server) getAgentsWarnings(info *agent_tracker.ConnectedAgentkInfo) []*rpc.AgentWarning {
	// Currently only a warning for the agent version is implemented.
	if info.AgentMeta == nil {
		return nil
	}

	agentVersion, err := version.NewVersion(info.AgentMeta.Version)
	if err != nil {
		s.log.Debug("Version received from agentk is not a valid semver", logz.Error(err), slog.String("version", info.AgentMeta.Version))
		return nil
	}

	versionWarningType, versionWarning := version.WarningIfOutdatedAgent(s.serverVersion, agentVersion, s.gitlabReleasesList)
	switch versionWarningType { //nolint:exhaustive
	case version.AgentVersionNoWarning:
		return nil
	default:
		return []*rpc.AgentWarning{
			{
				Warning: &rpc.AgentWarning_Version{
					Version: &rpc.AgentWarningVersion{
						Type:    string(versionWarningType),
						Message: versionWarning,
					},
				},
			},
		}
	}
}
