package supportbundlesimpl

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"context"
	"errors"
	"io"
	"testing"

	"filippo.io/age"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/infra/kvstore"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/tracing"
	"github.com/grafana/grafana/pkg/services/supportbundles"
	"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/setting"
)

const (
	testAgePublicKey   = "age15xgfm9gg89nz92pqjat2hy9h7lpwwfwwgvchg67lqtzzhjtmg5vq9utnd4"
	testAgePrivateKey  = "AGE-SECRET-KEY-1HGMLT8VSC95UXN2R5LUZECXT42WW7TSEYQKCWLX7PKH3YHS6HGCQ0XVEFD"
	testAgePublicKey2  = "age1q02sf508xetfa5ztzhuw0hxweyd50n27qndufaffvdth25knueds8w99c5"
	testAgePrivateKey2 = "AGE-SECRET-KEY-1DSW2P60F2ZRY4D4M57PEKTFVYCXXDYYZ0VZWG5RTUZCWHR3EJ9TQP92JXQ"
)

func TestService_bundleCreate(t *testing.T) {
	s := &Service{
		log:            log.New("test"),
		bundleRegistry: bundleregistry.ProvideService(),
		store:          newStore(kvstore.NewFakeKVStore()),
		tracer:         tracing.InitializeTracerForTest(),
	}

	cfg := setting.NewCfg()

	collector := basicCollector(cfg)
	disabledCollector := settingsCollector(setting.ProvideProvider(cfg))
	disabledCollector.EnabledFn = func() bool { return false }

	s.bundleRegistry.RegisterSupportItemCollector(collector)
	s.bundleRegistry.RegisterSupportItemCollector(disabledCollector)

	createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
	require.NoError(t, err)

	s.startBundleWork(context.Background(), []string{collector.UID, disabledCollector.UID}, createdBundle.UID)

	bundle, err := s.get(context.Background(), createdBundle.UID)
	require.NoError(t, err)

	assert.Equal(t, createdBundle.UID, bundle.UID)
	assert.Equal(t, supportbundles.StateComplete, bundle.State)
	assert.Equal(t, "bob", bundle.Creator)
	assert.NotZero(t, len(bundle.TarBytes))

	confirmFilesInTar(t, bundle.TarBytes)
}

func TestService_bundleEncryptDecrypt(t *testing.T) {
	s := &Service{
		log:                  log.New("test"),
		bundleRegistry:       bundleregistry.ProvideService(),
		store:                newStore(kvstore.NewFakeKVStore()),
		encryptionPublicKeys: []string{testAgePublicKey},
		tracer:               tracing.InitializeTracerForTest(),
	}

	cfg := setting.NewCfg()

	collector := basicCollector(cfg)
	s.bundleRegistry.RegisterSupportItemCollector(collector)

	createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
	require.NoError(t, err)

	s.startBundleWork(context.Background(), []string{collector.UID}, createdBundle.UID)

	bundle, err := s.get(context.Background(), createdBundle.UID)
	require.NoError(t, err)

	assert.Equal(t, createdBundle.UID, bundle.UID)
	assert.Equal(t, supportbundles.StateComplete, bundle.State)
	assert.Equal(t, "bob", bundle.Creator)
	assert.NotZero(t, len(bundle.TarBytes))

	tarBytes := decryptTar(t, bundle.TarBytes, testAgePrivateKey)
	assert.NotZero(t, len(tarBytes))

	confirmFilesInTar(t, tarBytes)
}

func TestService_bundleEncryptDecryptMultipleRecipients(t *testing.T) {
	s := &Service{
		log:                  log.New("test"),
		bundleRegistry:       bundleregistry.ProvideService(),
		store:                newStore(kvstore.NewFakeKVStore()),
		encryptionPublicKeys: []string{testAgePublicKey, testAgePublicKey2},
		tracer:               tracing.InitializeTracerForTest(),
	}

	cfg := setting.NewCfg()

	collector := basicCollector(cfg)
	s.bundleRegistry.RegisterSupportItemCollector(collector)

	createdBundle, err := s.store.Create(context.Background(), &user.SignedInUser{UserID: 1, Login: "bob"})
	require.NoError(t, err)

	s.startBundleWork(context.Background(), []string{collector.UID}, createdBundle.UID)

	bundle, err := s.get(context.Background(), createdBundle.UID)
	require.NoError(t, err)

	assert.Equal(t, createdBundle.UID, bundle.UID)
	assert.Equal(t, supportbundles.StateComplete, bundle.State)
	assert.Equal(t, "bob", bundle.Creator)
	assert.NotZero(t, len(bundle.TarBytes))

	tarBytes := decryptTar(t, bundle.TarBytes, testAgePrivateKey)
	assert.NotZero(t, len(tarBytes))

	confirmFilesInTar(t, tarBytes)

	tarBytes2 := decryptTar(t, bundle.TarBytes, testAgePrivateKey2)
	assert.NotZero(t, len(tarBytes2))

	confirmFilesInTar(t, tarBytes2)
}

func decryptTar(t *testing.T, tarBytes []byte, privateKey string) []byte {
	reader := bytes.NewReader(tarBytes)
	t.Helper()
	recipientPK, err := age.ParseX25519Identity(privateKey)
	require.NoError(t, err)

	tarBytesReader, err := age.Decrypt(reader, recipientPK)
	require.NoError(t, err)

	newTarBytes, err := io.ReadAll(tarBytesReader)
	require.NoError(t, err)
	return newTarBytes
}

// Check that the tarball contains the expected files
func confirmFilesInTar(t *testing.T, tarBytes []byte) {
	t.Helper()
	r := bytes.NewReader(tarBytes)
	gzipReader, err := gzip.NewReader(r)
	require.NoError(t, err)

	tr := tar.NewReader(gzipReader)
	files := []string{}
	for {
		hdr, err := tr.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		require.NoError(t, err)

		files = append(files, hdr.Name)
	}

	assert.ElementsMatch(t, []string{"/bundle/basic.json"}, files)
}
