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

package grpc_test

import (
	"context"
	_ "embed"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"testing"
	"time"

	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/siderolabs/go-api-signature/pkg/message"
	"github.com/siderolabs/go-api-signature/pkg/pgp"
	"github.com/siderolabs/go-api-signature/pkg/serviceaccount"
	clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config"
	machineryconfig "github.com/siderolabs/talos/pkg/machinery/config"
	talossecrets "github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/siderolabs/omni/client/api/omni/management"
	pkgaccess "github.com/siderolabs/omni/client/pkg/access"
	"github.com/siderolabs/omni/client/pkg/client"
	managementclient "github.com/siderolabs/omni/client/pkg/client/management"
	"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
	grpcomni "github.com/siderolabs/omni/internal/backend/grpc"
	"github.com/siderolabs/omni/internal/backend/runtime/kubernetes"
	omniruntime "github.com/siderolabs/omni/internal/backend/runtime/omni"
	omnictrl "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni"
	"github.com/siderolabs/omni/internal/pkg/auth"
	"github.com/siderolabs/omni/internal/pkg/auth/role"
	"github.com/siderolabs/omni/internal/pkg/config"
	"github.com/siderolabs/omni/internal/pkg/ctxstore"
)

//go:embed testdata/admin-kubeconfig.yaml
var adminKubeconfig []byte

func TestGenerateConfigs(t *testing.T) {
	ctx, cancel := context.WithTimeout(t.Context(), time.Second*15)
	defer cancel()

	logger := zaptest.NewLogger(t)
	st, err := omniruntime.NewTestState(logger)
	require.NoError(t, err)

	kubernetesRuntime := kubernetes.New(st.Default(), logger, "", "", "")

	rt, err := omniruntime.NewRuntime(config.Default(), nil, nil, nil,
		nil, nil, nil, nil, nil, st, prometheus.NewRegistry(),
		nil, kubernetesRuntime, nil, logger.WithOptions(zap.IncreaseLevel(zap.InfoLevel)))
	require.NoError(t, err)

	clusterName := "cluster1"
	saName := "serviceaccount1"
	serviceAccountEmail := saName + pkgaccess.ServiceAccountNameSuffix

	key, err := pgp.GenerateKey(saName, "", serviceAccountEmail, auth.ServiceAccountMaxAllowedLifetime)
	require.NoError(t, err)

	encodedKey, err := serviceaccount.Encode(saName, key)
	require.NoError(t, err)

	addressDisabled := runServer(t, st.Default(), false, kubernetesRuntime, rt)
	addressEnabled := runServer(t, st.Default(), true, kubernetesRuntime, rt)

	cDisabled, err := client.New(addressDisabled, client.WithServiceAccount(encodedKey))
	require.NoError(t, err)

	defer func() {
		require.NoError(t, cDisabled.Close())
	}()

	cEnabled, err := client.New(addressEnabled, client.WithServiceAccount(encodedKey))
	require.NoError(t, err)

	defer func() {
		require.NoError(t, cEnabled.Close())
	}()

	clientDisabled := cDisabled.Management().WithCluster(clusterName)
	clientEnabled := cEnabled.Management().WithCluster(clusterName)

	adminCtx := metadata.AppendToOutgoingContext(ctx, "role", string(role.Admin))
	readerCtx := metadata.AppendToOutgoingContext(ctx, "role", string(role.Reader))

	t.Run("kubeconfig not enabled", func(t *testing.T) {
		_, err = clientDisabled.Kubeconfig(adminCtx, managementclient.WithBreakGlassKubeconfig(true))

		require.Error(t, err)
		require.Equal(t, codes.PermissionDenied, status.Code(err), err)
	})

	t.Run("kubeconfig enabled no cluster", func(t *testing.T) {
		_, err = clientEnabled.Kubeconfig(adminCtx, managementclient.WithBreakGlassKubeconfig(true))

		require.Error(t, err)
	})

	t.Run("kubeconfig enabled success", func(t *testing.T) {
		cluster := omni.NewCluster(clusterName)
		require.NoError(t, st.Default().Create(ctx, cluster))

		clusterStatus := omni.NewClusterStatus(clusterName)
		require.NoError(t, st.Default().Create(ctx, clusterStatus, state.WithCreateOwner(omnictrl.ClusterStatusControllerName)))

		kubeconfigResource := omni.NewKubeconfig(clusterName)
		kubeconfigResource.TypedSpec().Value.Data = adminKubeconfig

		require.NoError(t, st.Default().Create(ctx, kubeconfigResource))

		var kubeconfig []byte

		kubeconfig, err = clientEnabled.Kubeconfig(adminCtx, managementclient.WithBreakGlassKubeconfig(true))
		require.NoError(t, err)

		_, err = clientcmd.Load(kubeconfig)
		require.NoError(t, err)

		updatedClusterStatus, err := safe.ReaderGetByID[*omni.ClusterStatus](ctx, st.Default(), clusterName) // nolint:govet
		require.NoError(t, err)

		_, taintBreakGlass := updatedClusterStatus.Metadata().Labels().Get(omni.LabelClusterTaintedByBreakGlass)
		require.True(t, taintBreakGlass)

		require.NoError(t, st.Default().Destroy(ctx, cluster.Metadata()))
		require.NoError(t, st.Default().Destroy(ctx, clusterStatus.Metadata(), state.WithDestroyOwner(omnictrl.ClusterStatusControllerName)))
	})

	t.Run("kubeconfig enabled no auth", func(t *testing.T) {
		_, err = clientEnabled.Kubeconfig(readerCtx, managementclient.WithBreakGlassKubeconfig(true))

		require.Error(t, err)
		require.Equal(t, codes.PermissionDenied, status.Code(err))
	})

	t.Run("talosconfig enabled no auth", func(t *testing.T) {
		_, err = clientEnabled.Talosconfig(readerCtx, managementclient.WithBreakGlassTalosconfig(true))

		require.Error(t, err)
		require.Equal(t, codes.PermissionDenied, status.Code(err))
	})

	t.Run("talosconfig enabled success", func(t *testing.T) {
		cluster := omni.NewCluster(clusterName)
		require.NoError(t, st.Default().Create(ctx, cluster))

		clusterStatus := omni.NewClusterStatus(clusterName)
		require.NoError(t, st.Default().Create(ctx, clusterStatus, state.WithCreateOwner(omnictrl.ClusterStatusControllerName)))

		secrets := omni.NewClusterSecrets(clusterName)

		bundle, err := talossecrets.NewBundle(talossecrets.NewFixedClock(time.Now()), machineryconfig.TalosVersion1_7)
		require.NoError(t, err)

		var data []byte

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

		secrets.TypedSpec().Value.Data = data

		require.NoError(t, st.Default().Create(ctx, secrets))

		var cfg []byte

		cfg, err = clientEnabled.Talosconfig(adminCtx, managementclient.WithBreakGlassTalosconfig(true))
		require.NoError(t, err)

		var talosConfig *clientconfig.Config

		talosConfig, err = clientconfig.FromBytes(cfg)
		require.NoError(t, err)

		require.NotEmpty(t, talosConfig.Contexts)

		updatedClusterStatus, err := safe.ReaderGetByID[*omni.ClusterStatus](ctx, st.Default(), clusterName)
		require.NoError(t, err)

		_, taintBreakGlass := updatedClusterStatus.Metadata().Labels().Get(omni.LabelClusterTaintedByBreakGlass)
		require.True(t, taintBreakGlass)

		require.NoError(t, st.Default().Destroy(ctx, cluster.Metadata()))
		require.NoError(t, st.Default().Destroy(ctx, clusterStatus.Metadata(), state.WithDestroyOwner(omnictrl.ClusterStatusControllerName)))
	})
}

func runServer(t *testing.T, st state.State, enableBreakGlassConfigs bool, kubernetesRuntime grpcomni.KubernetesRuntime,
	talosconfigProvider grpcomni.TalosconfigProvider, opts ...grpc.ServerOption,
) string {
	var err error

	listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", "localhost:0")
	require.NoError(t, err)

	grpcAddress := fmt.Sprintf("grpc://%s", listener.Addr())

	logger := zaptest.NewLogger(t)

	opts = append(opts,
		grpc.UnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
			md, ok := metadata.FromIncomingContext(ctx)
			if !ok {
				md = metadata.New(nil)
			}

			ctx = ctxstore.WithValue(ctx, auth.EnabledAuthContextKey{Enabled: true})

			msg := message.NewGRPC(md, info.FullMethod)

			ctx = ctxstore.WithValue(ctx, auth.GRPCMessageContextKey{Message: msg})

			if r := md.Get("role"); len(r) > 0 {
				var parsed role.Role

				parsed, err = role.Parse(r[0])
				if err != nil {
					return nil, err
				}

				ctx = ctxstore.WithValue(ctx, auth.RoleContextKey{Role: parsed})
			}

			return handler(ctx, req)
		}),
	)

	server := grpc.NewServer(opts...)

	management.RegisterManagementServiceServer(server, grpcomni.NewManagementServer(
		st,
		nil,
		logger,
		enableBreakGlassConfigs,
		kubernetesRuntime,
		talosconfigProvider,
	))

	var eg errgroup.Group

	eg.Go(func() error {
		for {
			err = server.Serve(listener)
			if err == nil || errors.Is(err, grpc.ErrServerStopped) {
				return nil
			}
		}
	})

	t.Cleanup(func() {
		server.Stop()

		require.NoError(t, eg.Wait())
	})

	return grpcAddress
}
