// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.

package grpc

import (
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/cosi-project/runtime/pkg/resource"
	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"
	gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/siderolabs/gen/optional"
	"github.com/siderolabs/go-kubernetes/kubernetes/upgrade"
	"github.com/siderolabs/talos/pkg/machinery/api/common"
	"github.com/siderolabs/talos/pkg/machinery/client"
	"github.com/zitadel/oidc/v3/pkg/op"
	"go.uber.org/zap"
	"go.yaml.in/yaml/v4"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"k8s.io/client-go/rest"

	commonOmni "github.com/siderolabs/omni/client/api/common"
	"github.com/siderolabs/omni/client/api/omni/management"
	"github.com/siderolabs/omni/client/api/omni/specs"
	"github.com/siderolabs/omni/client/pkg/constants"
	"github.com/siderolabs/omni/client/pkg/jointoken"
	omnires "github.com/siderolabs/omni/client/pkg/omni/resources/omni"
	siderolinkres "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink"
	ctlcfg "github.com/siderolabs/omni/client/pkg/omnictl/config"
	"github.com/siderolabs/omni/client/pkg/siderolink"
	"github.com/siderolabs/omni/internal/backend/dns"
	"github.com/siderolabs/omni/internal/backend/grpc/router"
	"github.com/siderolabs/omni/internal/backend/imagefactory"
	"github.com/siderolabs/omni/internal/backend/installimage"
	"github.com/siderolabs/omni/internal/backend/runtime/kubernetes"
	"github.com/siderolabs/omni/internal/backend/runtime/omni"
	"github.com/siderolabs/omni/internal/backend/runtime/omni/audit/auditlog"
	omniCtrl "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni"
	"github.com/siderolabs/omni/internal/backend/runtime/talos"
	"github.com/siderolabs/omni/internal/pkg/auth"
	"github.com/siderolabs/omni/internal/pkg/auth/accesspolicy"
	"github.com/siderolabs/omni/internal/pkg/auth/actor"
	"github.com/siderolabs/omni/internal/pkg/auth/role"
	"github.com/siderolabs/omni/internal/pkg/config"
	siderolinkinternal "github.com/siderolabs/omni/internal/pkg/siderolink"
)

// KubernetesRuntime provides kubernetes cluster access capabilities.
type KubernetesRuntime interface {
	GetKubeconfig(ctx context.Context, cluster *commonOmni.Context) (*rest.Config, error)
	GetOIDCKubeconfig(context *commonOmni.Context, identity string, extraOptions ...string) ([]byte, error)
	BreakGlassKubeconfig(ctx context.Context, id string) ([]byte, error)
	GetClient(ctx context.Context, cluster string) (*kubernetes.Client, error)
}

// TalosRuntime provides Talos cluster access capabilities.
type TalosRuntime interface {
	GetTalosconfigRaw(context *commonOmni.Context, identity string) ([]byte, error)
	GetClientForCluster(ctx context.Context, clusterName string) (*talos.Client, error)
	GetClientForMachine(ctx context.Context, machineID string) (*talos.Client, error)
}

// TalosconfigProvider provides raw and operator Talos configurations.
type TalosconfigProvider interface {
	RawTalosconfig(ctx context.Context, clusterName string) ([]byte, error)
	OperatorTalosconfig(ctx context.Context, clusterName string) ([]byte, error)
}

// JWTSigningKeyProvider is an interface for a JWT signing key provider.
type JWTSigningKeyProvider interface {
	SigningKey(ctx context.Context) (op.SigningKey, error)
}

func newManagementServer(cfg *config.Params, omniState state.State, jwtSigningKeyProvider JWTSigningKeyProvider, logHandler *siderolinkinternal.LogHandler, logger *zap.Logger,
	dnsService *dns.Service, imageFactoryClient *imagefactory.Client, auditor AuditLogger, omniconfigDest string,
	kubernetesRuntime KubernetesRuntime, talosRuntime TalosRuntime, talosconfigProvider TalosconfigProvider,
) *managementServer {
	return &managementServer{
		cfg:                   cfg,
		omniState:             omniState,
		jwtSigningKeyProvider: jwtSigningKeyProvider,
		logHandler:            logHandler,
		logger:                logger,
		dnsService:            dnsService,
		imageFactoryClient:    imageFactoryClient,
		auditor:               auditor,
		omniconfigDest:        omniconfigDest,
		kubernetesRuntime:     kubernetesRuntime,
		talosRuntime:          talosRuntime,
		talosconfigProvider:   talosconfigProvider,
	}
}

// managementServer implements omni management service.
type managementServer struct {
	management.UnimplementedManagementServiceServer
	cfg                   *config.Params
	kubernetesRuntime     KubernetesRuntime
	omniState             state.State
	jwtSigningKeyProvider JWTSigningKeyProvider
	auditor               AuditLogger
	talosconfigProvider   TalosconfigProvider
	talosRuntime          TalosRuntime
	logHandler            *siderolinkinternal.LogHandler
	logger                *zap.Logger
	dnsService            *dns.Service
	imageFactoryClient    *imagefactory.Client
	omniconfigDest        string
}

func (s *managementServer) register(server grpc.ServiceRegistrar) {
	management.RegisterManagementServiceServer(server, s)
}

func (s *managementServer) gateway(ctx context.Context, mux *gateway.ServeMux, address string, opts []grpc.DialOption) error {
	return management.RegisterManagementServiceHandlerFromEndpoint(ctx, mux, address, opts)
}

func (s *managementServer) Kubeconfig(ctx context.Context, req *management.KubeconfigRequest) (*management.KubeconfigResponse, error) {
	if req.BreakGlass {
		return s.breakGlassKubeconfig(ctx)
	}

	commonContext := router.ExtractContext(ctx)

	clusterName := ""
	if commonContext != nil {
		clusterName = commonContext.Name
	}

	ctx, err := accesspolicy.ApplyClusterAccessPolicy(ctx, clusterName, s.omniState)
	if err != nil {
		return nil, err
	}

	if req.GetServiceAccount() {
		return s.serviceAccountKubeconfig(ctx, req)
	}

	// not a service account, generate OIDC (user) or admin kubeconfig

	authResult, err := auth.CheckGRPC(ctx, auth.WithRole(role.Reader))
	if err != nil {
		return nil, err
	}

	var extraOptions []string

	if req.GrantType != "" {
		switch req.GrantType {
		case "auto":
		case "authcode":
		case "authcode-keyboard":
		default:
			return nil, status.Errorf(codes.InvalidArgument, "invalid grant type %q", req.GrantType)
		}

		extraOptions = []string{
			"grant-type=" + req.GrantType,
			"oidc-redirect-url=urn:ietf:wg:oauth:2.0:oob",
		}
	}

	// Resolve OIDC cache directory: request fields (from omnictl) take priority over server config.
	cacheDir := req.OidcCacheBaseDir
	if cacheDir == "" {
		cacheDir = s.cfg.Services.KubernetesProxy.GetOidcCacheBaseDir()
	}

	cacheIsolation := req.OidcCacheIsolation
	if !cacheIsolation {
		cacheIsolation = s.cfg.Services.KubernetesProxy.GetOidcCacheIsolation()
	}

	if cacheIsolation {
		if cacheDir == "" {
			cacheDir = filepath.Join("~", ".kube", "cache", "oidc-login")
		}

		cacheDir = filepath.Join(cacheDir, s.cfg.Account.GetName()+"-"+commonContext.Name+"-"+authResult.Identity)
	}

	if cacheDir != "" {
		extraOptions = append(extraOptions, "token-cache-dir="+cacheDir)
	}

	kubeconfig, err := s.kubernetesRuntime.GetOIDCKubeconfig(commonContext, authResult.Identity, extraOptions...)
	if err != nil {
		return nil, err
	}

	return &management.KubeconfigResponse{
		Kubeconfig: kubeconfig,
	}, nil
}

func (s *managementServer) Talosconfig(ctx context.Context, request *management.TalosconfigRequest) (*management.TalosconfigResponse, error) {
	r := role.Reader
	if request.BreakGlass {
		r = role.Admin
	}

	// getting talosconfig is low risk, as it doesn't contain any sensitive data
	// real check for authentication happens in the Talos API gRPC proxy
	authResult, err := auth.CheckGRPC(ctx, auth.WithRole(r))
	if err != nil {
		return nil, err
	}

	// this one is not low-risk, but it works only in debug mode
	switch {
	case request.Raw:
		return s.breakGlassTalosconfig(ctx, true)
	case request.BreakGlass:
		return s.breakGlassTalosconfig(ctx, false)
	}

	talosconfig, err := s.talosRuntime.GetTalosconfigRaw(router.ExtractContext(ctx), authResult.Identity)
	if err != nil {
		return nil, err
	}

	return &management.TalosconfigResponse{
		Talosconfig: talosconfig,
	}, nil
}

func (s *managementServer) Omniconfig(ctx context.Context, _ *emptypb.Empty) (*management.OmniconfigResponse, error) {
	// getting omniconfig is low risk, since it only contains parameters already known by the user
	authResult, err := auth.CheckGRPC(ctx, auth.WithValidSignature(true))
	if err != nil {
		return nil, err
	}

	cfg, err := generateConfig(authResult, s.omniconfigDest)
	if err != nil {
		return nil, err
	}

	return &management.OmniconfigResponse{
		Omniconfig: cfg,
	}, nil
}

func (s *managementServer) MachineLogs(request *management.MachineLogsRequest, serv grpc.ServerStreamingServer[common.Data]) error {
	ctx := serv.Context()

	// getting machine logs is equivalent to reading machine resource
	if _, err := auth.CheckGRPC(ctx, auth.WithRole(role.Reader)); err != nil {
		return err
	}

	machineID := request.GetMachineId()
	if machineID == "" {
		return status.Error(codes.InvalidArgument, "machine id is required")
	}

	tailLines := optional.None[int32]()
	if request.TailLines >= 0 {
		tailLines = optional.Some(request.TailLines)
	}

	logReader, err := s.logHandler.GetReader(ctx, siderolinkinternal.MachineID(machineID), request.Follow, tailLines)
	if err != nil {
		return handleError(err)
	}

	defer logReader.Close() //nolint:errcheck

	for {
		line, err := logReader.ReadLine(ctx)
		if err != nil {
			return handleError(err)
		}

		if err := serv.Send(&common.Data{
			Bytes: line,
		}); err != nil {
			return err
		}
	}
}

func (s *managementServer) ValidateConfig(ctx context.Context, request *management.ValidateConfigRequest) (*emptypb.Empty, error) {
	// validating machine config is low risk, require any valid signature
	if _, err := auth.CheckGRPC(ctx, auth.WithValidSignature(true)); err != nil {
		return nil, err
	}

	if err := omnires.ValidateConfigPatch([]byte(request.Config)); err != nil {
		return nil, status.Error(codes.InvalidArgument, err.Error())
	}

	return &emptypb.Empty{}, nil
}

func (s *managementServer) markClusterAsTainted(ctx context.Context, name string) error {
	ctx = actor.MarkContextAsInternalActor(ctx)

	if _, err := safe.StateUpdateWithConflicts(
		ctx,
		s.omniState,
		omnires.NewClusterStatus(name).Metadata(),
		func(res *omnires.ClusterStatus) error {
			res.Metadata().Labels().Set(omnires.LabelClusterTaintedByBreakGlass, "")
			res.Metadata().Annotations().Set(omnires.TaintedByBreakGlassTimestamp, strconv.FormatInt(time.Now().Unix(), 10))

			return nil
		},
		state.WithUpdateOwner(omniCtrl.ClusterStatusControllerName),
	); err != nil {
		return err
	}

	return nil
}

func (s *managementServer) getRaw(ctx context.Context, clusterName string) ([]byte, error) {
	if !constants.IsDebugBuild {
		return nil, status.Error(codes.PermissionDenied, "not allowed")
	}

	return s.talosconfigProvider.RawTalosconfig(ctx, clusterName)
}

func (s *managementServer) getBreakGlass(ctx context.Context, clusterName string) ([]byte, error) {
	return s.talosconfigProvider.OperatorTalosconfig(ctx, clusterName)
}

func (s *managementServer) breakGlassTalosconfig(ctx context.Context, raw bool) (*management.TalosconfigResponse, error) {
	if !constants.IsDebugBuild && !s.cfg.Features.GetEnableBreakGlassConfigs() {
		return nil, status.Error(codes.PermissionDenied, "not allowed")
	}

	routerContext := router.ExtractContext(ctx)

	if routerContext == nil || routerContext.Name == "" {
		return nil, status.Error(codes.InvalidArgument, "cluster name is required")
	}

	clusterName := routerContext.Name

	var (
		data []byte
		err  error
	)

	if raw {
		data, err = s.getRaw(ctx, clusterName)
	} else {
		data, err = s.getBreakGlass(ctx, clusterName)
	}

	if err != nil {
		return nil, err
	}

	if err = s.markClusterAsTainted(ctx, clusterName); err != nil {
		return nil, err
	}

	return &management.TalosconfigResponse{
		Talosconfig: data,
	}, nil
}

func (s *managementServer) breakGlassKubeconfig(ctx context.Context) (*management.KubeconfigResponse, error) {
	_, err := auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
	if err != nil {
		return nil, err
	}

	if !constants.IsDebugBuild && !s.cfg.Features.GetEnableBreakGlassConfigs() {
		return nil, status.Error(codes.PermissionDenied, "not allowed")
	}

	routerContext := router.ExtractContext(ctx)

	if routerContext == nil || routerContext.Name == "" {
		return nil, status.Error(codes.InvalidArgument, "cluster name is required")
	}

	clusterName := routerContext.Name

	data, err := s.kubernetesRuntime.BreakGlassKubeconfig(ctx, clusterName)
	if err != nil {
		return nil, err
	}

	if err = s.markClusterAsTainted(ctx, clusterName); err != nil {
		return nil, err
	}

	return &management.KubeconfigResponse{
		Kubeconfig: data,
	}, nil
}

func (s *managementServer) KubernetesUpgradePreChecks(ctx context.Context, req *management.KubernetesUpgradePreChecksRequest) (*management.KubernetesUpgradePreChecksResponse, error) {
	if _, err := s.authCheckGRPC(ctx, auth.WithRole(role.Operator)); err != nil {
		return nil, err
	}

	ctx = actor.MarkContextAsInternalActor(ctx)

	requestContext := router.ExtractContext(ctx)
	if requestContext == nil {
		return nil, status.Error(codes.InvalidArgument, "unable to extract request context")
	}

	upgradeStatus, err := safe.StateGet[*omnires.KubernetesUpgradeStatus](ctx, s.omniState, omnires.NewKubernetesUpgradeStatus(requestContext.Name).Metadata())
	if err != nil {
		return nil, err
	}

	currentVersion := upgradeStatus.TypedSpec().Value.LastUpgradeVersion
	if currentVersion == "" {
		return nil, status.Error(codes.FailedPrecondition, "current version is not known yet")
	}

	path, err := upgrade.NewPath(currentVersion, req.NewVersion)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "invalid upgrade path: %v", err)
	}

	if !path.IsSupported() {
		return nil, status.Errorf(codes.InvalidArgument, "unsupported upgrade path: %s", path)
	}

	restConfig, err := s.kubernetesRuntime.GetKubeconfig(ctx, requestContext)
	if err != nil {
		return nil, fmt.Errorf("error getting kubeconfig: %w", err)
	}

	var controlplaneMachines []string

	cms, err := safe.StateListAll[*omnires.ClusterMachine](
		ctx,
		s.omniState,
		state.WithLabelQuery(
			resource.LabelEqual(omnires.LabelCluster, requestContext.Name),
			resource.LabelExists(omnires.LabelControlPlaneRole),
		),
	)
	if err != nil {
		return nil, err
	}

	for cm := range cms.All() {
		controlplaneMachines = append(controlplaneMachines, cm.Metadata().ID())
	}

	s.logger.Debug("running k8s upgrade pre-checks", zap.Strings("controlplane_machines", controlplaneMachines), zap.String("cluster", requestContext.Name))

	var logBuffer strings.Builder

	preCheck, err := upgrade.NewChecksWithStateProvider(path, func(ctx context.Context, machineID string) (state.State, error) {
		c, clientErr := s.talosRuntime.GetClientForMachine(ctx, machineID)
		if clientErr != nil {
			return nil, clientErr
		}

		return c.COSI, nil
	}, restConfig, controlplaneMachines, nil, func(format string, args ...any) {
		fmt.Fprintf(&logBuffer, format, args...)
		fmt.Fprintln(&logBuffer)
	})
	if err != nil {
		return nil, err
	}

	if err = preCheck.Run(ctx); err != nil {
		s.logger.Error("failed running pre-checks", zap.String("log", logBuffer.String()), zap.String("cluster", requestContext.Name), zap.Error(err))

		fmt.Fprintf(&logBuffer, "pre-checks failed: %v\n", err)

		return &management.KubernetesUpgradePreChecksResponse{
			Ok:     false,
			Reason: logBuffer.String(),
		}, nil
	}

	s.logger.Debug("k8s upgrade pre-checks successful", zap.String("log", logBuffer.String()), zap.String("cluster", requestContext.Name))

	return &management.KubernetesUpgradePreChecksResponse{
		Ok: true,
	}, nil
}

// ReadAuditLog reads the audit log from the backend.
func (s *managementServer) ReadAuditLog(req *management.ReadAuditLogRequest, srv grpc.ServerStreamingServer[management.ReadAuditLogResponse]) error {
	ctx := srv.Context()

	if _, err := s.authCheckGRPC(ctx, auth.WithRole(role.Admin)); err != nil {
		return err
	}

	now := time.Now()

	start, err := parseTime(req.GetStartTime(), now.AddDate(0, 0, -29))
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "invalid start time: %v", err)
	}

	end, err := parseTime(req.GetEndTime(), now)
	if err != nil {
		return status.Errorf(codes.InvalidArgument, "invalid end time: %v", err)
	}

	rdr, err := s.auditor.Reader(ctx, start, end)
	if err != nil {
		return err
	}

	closeFn := sync.OnceValue(rdr.Close)
	defer closeFn() //nolint:errcheck

	for {
		data, err := rdr.Read()
		if err != nil {
			if errors.Is(err, io.EOF) {
				break
			}

			return err
		}

		if err = srv.Send(&management.ReadAuditLogResponse{AuditLog: data}); err != nil {
			return err
		}
	}

	return closeFn()
}

// MaintenanceUpgrade performs a maintenance upgrade on a specified machine.
func (s *managementServer) MaintenanceUpgrade(ctx context.Context, req *management.MaintenanceUpgradeRequest) (*management.MaintenanceUpgradeResponse, error) {
	s.logger.Info("maintenance upgrade request received", zap.String("machine_id", req.MachineId), zap.String("version", req.Version))

	if _, err := s.authCheckGRPC(ctx, auth.WithRole(role.Operator)); err != nil {
		return nil, err
	}

	if req.MachineId == "" {
		return nil, status.Error(codes.InvalidArgument, "machine id is required")
	}

	ctx = actor.MarkContextAsInternalActor(ctx)

	machineStatus, err := safe.StateGetByID[*omnires.MachineStatus](ctx, s.omniState, req.MachineId)
	if err != nil {
		if state.IsNotFoundError(err) {
			return nil, status.Error(codes.NotFound, "machine not found")
		}

		return nil, fmt.Errorf("failed to get machine status: %w", err)
	}

	if !machineStatus.TypedSpec().Value.Maintenance {
		return nil, status.Error(codes.FailedPrecondition, "machine is not in maintenance mode")
	}

	if !machineStatus.TypedSpec().Value.SchematicReady() {
		return nil, status.Error(codes.FailedPrecondition, "machine schematic is not ready yet")
	}

	platform := machineStatus.TypedSpec().Value.GetPlatformMetadata().GetPlatform()
	if platform == "" {
		return nil, status.Error(codes.FailedPrecondition, "machine platform is not known yet")
	}

	securityState := machineStatus.TypedSpec().Value.SecurityState
	if securityState == nil {
		return nil, status.Error(codes.FailedPrecondition, "machine security state is not known yet")
	}

	installImage := &specs.MachineConfigGenOptionsSpec_InstallImage{
		TalosVersion:         req.Version,
		SchematicId:          machineStatus.TypedSpec().Value.Schematic.FullId,
		SchematicInitialized: true,
		Platform:             platform,
		SecurityState:        securityState,
	}

	installImageStr, err := installimage.Build(s.imageFactoryClient.Host(), req.MachineId, installImage, s.cfg.Registries.GetTalos())
	if err != nil {
		return nil, fmt.Errorf("failed to build install image: %w", err)
	}

	s.logger.Info("built maintenance upgrade image", zap.String("image", installImageStr))

	address := machineStatus.TypedSpec().Value.ManagementAddress
	opts := talos.GetSocketOptions(address)
	opts = append(opts, client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}), client.WithEndpoints(address))

	talosClient, err := client.New(ctx, opts...)
	if err != nil {
		return nil, fmt.Errorf("failed to create talos client: %w", err)
	}

	//nolint:staticcheck
	if _, err = talosClient.UpgradeWithOptions(ctx, client.WithUpgradeImage(installImageStr)); err != nil {
		return nil, fmt.Errorf("failed to upgrade machine: %w", err)
	}

	return &management.MaintenanceUpgradeResponse{}, nil
}

func (s *managementServer) GetMachineJoinConfig(ctx context.Context, request *management.GetMachineJoinConfigRequest) (*management.GetMachineJoinConfigResponse, error) {
	ctx = actor.MarkContextAsInternalActor(ctx)

	apiConfig, err := safe.StateGetByID[*siderolinkres.APIConfig](ctx, s.omniState, siderolinkres.ConfigID)
	if err != nil {
		return nil, fmt.Errorf("failed to get APIConfig for the extra kernel arguments: %w", err)
	}

	if request.JoinToken == "" {
		var defaultToken *siderolinkres.DefaultJoinToken

		defaultToken, err = safe.StateGetByID[*siderolinkres.DefaultJoinToken](ctx, s.omniState, siderolinkres.DefaultJoinTokenID)
		if err != nil {
			return nil, fmt.Errorf("failed to get the default join token: %w", err)
		}

		request.JoinToken = defaultToken.TypedSpec().Value.TokenId
	}

	// If the tunnel is enabled instance-wide or in the request, the final state is enabled
	opts, err := siderolink.NewJoinOptions(
		siderolink.WithMachineAPIURL(apiConfig.TypedSpec().Value.MachineApiAdvertisedUrl),
		siderolink.WithJoinToken(request.JoinToken),
		siderolink.WithGRPCTunnel(request.UseGrpcTunnel),
		siderolink.WithEventSinkPort(int(apiConfig.TypedSpec().Value.EventsPort)),
		siderolink.WithLogServerPort(int(apiConfig.TypedSpec().Value.LogsPort)),
	)
	if err != nil {
		return nil, err
	}

	config, err := opts.RenderJoinConfig()
	if err != nil {
		return nil, err
	}

	return &management.GetMachineJoinConfigResponse{
		Config:     string(config),
		KernelArgs: opts.GetKernelArgs(),
	}, nil
}

func (s *managementServer) CreateJoinToken(ctx context.Context, request *management.CreateJoinTokenRequest) (*management.CreateJoinTokenResponse, error) {
	_, err := auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
	if err != nil {
		return nil, err
	}

	token, err := jointoken.Generate()
	if err != nil {
		return nil, err
	}

	joinToken := siderolinkres.NewJoinToken(token)

	if request.Name == "" && len(request.Name) > omni.MaxJoinTokenNameLength {
		return nil, status.Error(codes.InvalidArgument, "token name is invalid")
	}

	joinToken.TypedSpec().Value.Name = request.Name
	joinToken.TypedSpec().Value.ExpirationTime = request.ExpirationTime

	if err = s.omniState.Create(actor.MarkContextAsInternalActor(ctx), joinToken); err != nil {
		return nil, err
	}

	return &management.CreateJoinTokenResponse{
		Id: token,
	}, nil
}

func (s *managementServer) ResetNodeUniqueToken(ctx context.Context, request *management.ResetNodeUniqueTokenRequest) (*management.ResetNodeUniqueTokenResponse, error) {
	_, err := auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
	if err != nil {
		return nil, err
	}

	_, err = safe.StateGetByID[*omnires.MachineStatus](ctx, s.omniState, request.Id)
	if err != nil {
		if state.IsNotFoundError(err) {
			return nil, status.Error(codes.NotFound, "machine not found")
		}

		return nil, fmt.Errorf("failed to get machine status: %w", err)
	}

	if err = s.omniState.TeardownAndDestroy(actor.MarkContextAsInternalActor(ctx), siderolinkres.NewNodeUniqueToken(request.Id).Metadata()); err != nil {
		return nil, fmt.Errorf("failed to teardown and destroy node unique token: %w", err)
	}

	return &management.ResetNodeUniqueTokenResponse{}, nil
}

func parseTime(date string, fallback time.Time) (time.Time, error) {
	if date == "" {
		return fallback, nil
	}

	result, err := time.ParseInLocation(time.DateOnly, date, time.Local) //nolint:gosmopolitan
	if err != nil {
		return time.Time{}, err
	}

	return result, nil
}

func (s *managementServer) triggerManifestResync(ctx context.Context, requestContext *commonOmni.Context) error {
	// trigger fake update in KubernetesUpgradeStatusType to force re-calculating the status
	// this is needed because the status is not updated when the rollout is finished
	_, err := safe.StateUpdateWithConflicts(
		ctx,
		s.omniState,
		omnires.NewKubernetesUpgradeStatus(requestContext.Name).Metadata(),
		func(res *omnires.KubernetesUpgradeStatus) error {
			res.Metadata().Annotations().Set("manifest-rollout", strconv.Itoa(int(time.Now().Unix())))

			return nil
		},
		state.WithUpdateOwner(omniCtrl.KubernetesUpgradeStatusControllerName),
	)
	if err != nil && !state.IsNotFoundError(err) {
		return fmt.Errorf("failed to update KubernetesUpgradeStatus: %w", err)
	}

	return nil
}

//nolint:unparam
func (s *managementServer) authCheckGRPC(ctx context.Context, opts ...auth.CheckOption) (auth.CheckResult, error) {
	authCheckResult, err := auth.Check(ctx, opts...)
	if errors.Is(err, auth.ErrUnauthenticated) {
		return auth.CheckResult{}, status.Error(codes.Unauthenticated, err.Error())
	}

	if errors.Is(err, auth.ErrUnauthorized) {
		return auth.CheckResult{}, status.Error(codes.PermissionDenied, err.Error())
	}

	if err != nil {
		return auth.CheckResult{}, err
	}

	return authCheckResult, nil
}

func handleError(err error) error {
	switch {
	case errors.Is(err, io.EOF):
		return nil
	case errors.Is(err, siderolinkinternal.ErrLogStoreNotFound):
		return status.Error(codes.NotFound, err.Error())
	}

	return err
}

func generateConfig(authResult auth.CheckResult, contextURL string) ([]byte, error) {
	// This is safe to do, since omnictl config pkg doesn't import anything from the backend
	cfg := &ctlcfg.Config{
		Contexts: map[string]*ctlcfg.Context{
			"default": {
				URL: contextURL,
				Auth: ctlcfg.Auth{
					SideroV1: ctlcfg.SideroV1{
						Identity: authResult.Identity,
					},
				},
			},
		},
		Context: "default",
	}

	result, err := yaml.Marshal(cfg)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal omnicfg: %w", err)
	}

	return result, error(nil)
}

func generateDest(apiurl string) (string, error) {
	parsedDest, err := url.Parse(apiurl)
	if err != nil {
		return "", fmt.Errorf("incorrect destination: %w", err)
	}

	result := parsedDest.String()
	if result == "" {
		// This can happen if Parse actually failed but didn't return an error
		return "", fmt.Errorf("incorrect destination '%s'", parsedDest)
	}

	return result, nil
}

// AuditLogger is an interface for reading the audit log.
type AuditLogger interface {
	Reader(ctx context.Context, start, end time.Time) (auditlog.Reader, error)
}
