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

//go:build integration

package integration_test

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/google/uuid"
	"github.com/siderolabs/go-api-signature/pkg/pgp"
	"github.com/siderolabs/go-api-signature/pkg/serviceaccount"
	"github.com/stretchr/testify/require"

	pkgaccess "github.com/siderolabs/omni/client/pkg/access"
	"github.com/siderolabs/omni/client/pkg/client"
	"github.com/siderolabs/omni/client/pkg/omni/resources/omni"
	"github.com/siderolabs/omni/internal/pkg/auth"
	"github.com/siderolabs/omni/internal/pkg/auth/role"
)

// AssertDownloadUsingCLI verifies generated image download using omnictl.
func AssertDownloadUsingCLI(testCtx context.Context, client *client.Client, omnictlPath, httpEndpoint string) TestFunc {
	st := client.Omni().State()

	return func(t *testing.T) {
		t.Parallel()

		if omnictlPath == "" {
			t.Skip()
		}

		media, err := safe.StateListAll[*omni.InstallationMedia](testCtx, st)
		require.NoError(t, err)

		var images []*omni.InstallationMedia

		for val := range media.All() {
			spec := val.TypedSpec().Value

			switch spec.Profile {
			case "aws":
				fallthrough
			case "iso":
				images = append(images, val)
			}

			if spec.Overlay == "rpi_generic" {
				images = append(images, val)
			}
		}

		require.Greater(t, len(images), 2)

		name := "test-" + uuid.NewString()

		key := createServiceAccount(testCtx, t, client, name, role.Admin)

		for _, image := range images {
			t.Run(image.Metadata().ID(), func(t *testing.T) {
				t.Parallel()

				output := filepath.Join(t.TempDir(), image.Metadata().ID())

				stdout, stderr, err := runCmd(
					omnictlPath,
					httpEndpoint,
					key, "download",
					"--insecure-skip-tls-verify",
					image.TypedSpec().Value.Name,
					"--initial-labels",
					"key=value",
					"--output",
					output)
				require.NoError(t, err, "stdout:\n %s\nstderr:\n%s", stdout.String(), stderr.String())

				res, err := os.Stat(output)

				require.NoError(t, err)

				require.Greater(t, res.Size(), int64(1024*1024))
			})
		}
	}
}

func runCmd(path, endpoint, key string, args ...string) (bytes.Buffer, bytes.Buffer, error) {
	var stdout, stderr bytes.Buffer

	args = append([]string{"--insecure-skip-tls-verify"}, args...)

	cmd := exec.Command(
		path,
		args...,
	)

	cmd.Stdin = nil
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	tempHomeDir, err := os.MkdirTemp("", "cli-test")
	if err != nil {
		return stdout, stderr, fmt.Errorf("failed to create temp home dir: %w", err)
	}
	defer os.RemoveAll(tempHomeDir)

	cmd.Env = []string{
		fmt.Sprintf("HOME=%s", tempHomeDir),
		fmt.Sprintf("OMNI_ENDPOINT=%s", endpoint),
		fmt.Sprintf("OMNI_SERVICE_ACCOUNT_KEY=%s", key),
	}

	if err := cmd.Start(); err != nil {
		return stdout, stderr, err
	}

	err = cmd.Wait()

	return stdout, stderr, err
}

func createServiceAccount(ctx context.Context, t *testing.T, client *client.Client, name string, role role.Role) string {
	// generate a new PGP key with long lifetime
	comment := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)

	serviceAccountEmail := name + pkgaccess.ServiceAccountNameSuffix

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

	armoredPublicKey, err := key.ArmorPublic()

	require.NoError(t, err)

	// create service account with the generated key
	_, err = client.Management().CreateServiceAccount(ctx, name, armoredPublicKey, string(role), false)
	require.NoError(t, err)

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

	return encodedKey
}

// AssertUserCLI verifies user management cli commands.
func AssertUserCLI(testCtx context.Context, client *client.Client, omnictlPath, httpEndpoint string) TestFunc {
	return func(t *testing.T) {
		name := "test-" + uuid.NewString()

		key := createServiceAccount(testCtx, t, client, name, role.Admin)

		stdout, stderr, err := runCmd(omnictlPath, httpEndpoint, key, "user", "create", "a@a.com", "--role", "Admin")
		require.NoErrorf(t, err, "failed to create user. stdout: %q | stderr: %q", stdout.String(), stderr.String())

		stdout, stderr, err = runCmd(omnictlPath, httpEndpoint, key, "user", "list")
		require.NoErrorf(t, err, "failed to list users. stdout: %q | stderr: %q", stdout.String(), stderr.String())

		require.Contains(t, stdout.String(), "a@a.com")

		stdout, stderr, err = runCmd(omnictlPath, httpEndpoint, key, "user", "set-role", "--role", "Reader", "a@a.com")
		require.NoErrorf(t, err, "failed to set role. stdout: %q | stderr: %q", stdout.String(), stderr.String())

		stdout, stderr, err = runCmd(omnictlPath, httpEndpoint, key, "user", "delete", "a@a.com")
		require.NoErrorf(t, err, "failed to delete user. stdout: %q | stderr: %q", stdout.String(), stderr.String())

		stdout, stderr, err = runCmd(omnictlPath, httpEndpoint, key, "user", "list")
		require.NoErrorf(t, err, "failed to list users. stdout: %q | stderr: %q", stdout.String(), stderr.String())

		require.NotContains(t, stdout.String(), "a@a.com")
	}
}
