package kube

import (
	"context"
	"encoding/base64"
	"os"
	"path/filepath"
	"time"

	//nolint:staticcheck // ST1001
	. "github.com/onsi/ginkgo/v2"
	//nolint:staticcheck // ST1001
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/1Password/onepassword-operator/pkg/testhelper/system"
)

type Secret struct {
	client client.Client
	config *Config
	name   string
}

// CreateFromEnvVar creates a kubernetes secret from an environment variable
func (s *Secret) CreateFromEnvVar(ctx context.Context, envVar string) *corev1.Secret {
	By("Creating '" + s.name + "' secret from environment variable")

	// Derive a short-lived context so this API call won't hang indefinitely.
	c, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	value, ok := os.LookupEnv(envVar)
	Expect(ok).To(BeTrue())
	Expect(value).NotTo(BeEmpty())

	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      s.name,
			Namespace: s.config.Namespace,
		},
		StringData: map[string]string{
			"token": value,
		},
	}

	err := s.client.Create(c, secret)
	Expect(err).NotTo(HaveOccurred())

	return secret
}

// CreateFromFile creates a kubernetes secret from a file
func (s *Secret) CreateFromFile(ctx context.Context, fileName string, content []byte) *corev1.Secret {
	By("Creating '" + s.name + "' secret from file " + fileName)

	// Derive a short-lived context so this API call won't hang indefinitely.
	c, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      s.name,
			Namespace: s.config.Namespace,
		},
		Data: map[string][]byte{
			filepath.Base(fileName): content,
		},
	}

	err := s.client.Create(c, secret)
	Expect(err).NotTo(HaveOccurred())

	return secret
}

// CreateOpCredentials creates a kubernetes secret from 1password-credentials.json file in the project root
// encodes it in base64 and saves it to op-session file
func (s *Secret) CreateOpCredentials(ctx context.Context) *corev1.Secret {
	rootDir, err := system.GetProjectRoot()
	Expect(err).NotTo(HaveOccurred())

	credentialsFilePath := filepath.Join(rootDir, "1password-credentials.json")
	data, err := os.ReadFile(credentialsFilePath)
	Expect(err).NotTo(HaveOccurred())

	encoded := base64.RawURLEncoding.EncodeToString(data)

	return s.CreateFromFile(ctx, "op-session", []byte(encoded))
}

// Get retrieves a kubernetes secret
func (s *Secret) Get(ctx context.Context) *corev1.Secret {
	By("Getting '" + s.name + "' secret")

	// Derive a short-lived context so this API call won't hang indefinitely.
	c, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	secret := &corev1.Secret{}
	err := s.client.Get(c, client.ObjectKey{Name: s.name, Namespace: s.config.Namespace}, secret)
	Expect(err).NotTo(HaveOccurred())

	return secret
}

// Delete deletes a kubernetes secret
func (s *Secret) Delete(ctx context.Context) {
	By("Deleting '" + s.name + "' secret")

	// Derive a short-lived context so this API call won't hang indefinitely.
	c, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      s.name,
			Namespace: s.config.Namespace,
		},
	}
	err := s.client.Delete(c, secret)
	Expect(err).NotTo(HaveOccurred())
}

// CheckIfExists repeatedly attempts to retrieve the given Secret
// from the cluster until it is found or the test's timeout expires.
func (s *Secret) CheckIfExists(ctx context.Context) {
	By("Checking '" + s.name + "' secret")

	Eventually(func(g Gomega) {
		// Derive a short-lived context so this API call won't hang indefinitely.
		attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
		defer cancel()

		secret := &corev1.Secret{}
		err := s.client.Get(attemptCtx, client.ObjectKey{Name: s.name, Namespace: s.config.Namespace}, secret)
		g.Expect(err).NotTo(HaveOccurred())
	}, s.config.TestConfig.Timeout, s.config.TestConfig.Interval).Should(Succeed())
}
