package publish

import (
	"fmt"
	"strings"

	"github.com/getsops/sops/v3/logging"
	"github.com/google/go-cmp/cmp"
	vault "github.com/hashicorp/vault/api"

	"github.com/sirupsen/logrus"
)

var log *logrus.Logger

func init() {
	log = logging.NewLogger("PUBLISH")
}

type VaultDestination struct {
	vaultAddress string
	vaultPath    string
	kvMountName  string
	kvVersion    int
}

func NewVaultDestination(vaultAddress, vaultPath, kvMountName string, kvVersion int) *VaultDestination {
	if !strings.HasSuffix(vaultPath, "/") {
		vaultPath = vaultPath + "/"
	}
	if kvMountName == "" {
		kvMountName = "secret/"
	}
	if !strings.HasSuffix(kvMountName, "/") {
		kvMountName = kvMountName + "/"
	}
	if kvVersion != 1 && kvVersion != 2 {
		kvVersion = 2
	}
	return &VaultDestination{vaultAddress, vaultPath, kvMountName, kvVersion}
}

func (vaultd *VaultDestination) getAddress() string {
	if vaultd.vaultAddress != "" {
		return vaultd.vaultAddress
	}
	return vault.DefaultConfig().Address
}

func (vaultd *VaultDestination) Path(fileName string) string {
	return fmt.Sprintf("%s/v1/%s", vaultd.getAddress(), vaultd.secretsPath(fileName))
}

func (vaultd *VaultDestination) secretsPath(fileName string) string {
	if vaultd.kvVersion == 1 {
		return fmt.Sprintf("%s%s%s", vaultd.kvMountName, vaultd.vaultPath, fileName)
	}
	return fmt.Sprintf("%sdata/%s%s", vaultd.kvMountName, vaultd.vaultPath, fileName)
}

// Returns NotImplementedError
func (vaultd *VaultDestination) Upload(fileContents []byte, fileName string) error {
	return &NotImplementedError{"Vault does not support uploading encrypted sops files directly."}
}

func (vaultd *VaultDestination) UploadUnencrypted(data map[string]interface{}, fileName string) error {
	client, err := vault.NewClient(nil)
	if err != nil {
		return err
	}
	if vaultd.vaultAddress != "" {
		err = client.SetAddress(vaultd.vaultAddress)
		if err != nil {
			return err
		}
	}

	secretsPath := vaultd.secretsPath(fileName)
	existingSecret, err := client.Logical().Read(secretsPath)
	if err != nil {
		log.Warnf("Cannot check if destination secret already exists in %s. New version will be created even if the data has not been changed.", secretsPath)
	}
	if existingSecret != nil && cmp.Equal(data, existingSecret.Data["data"]) {
		log.Infof("Secret in %s is already up-to-date.\n", secretsPath)
		return nil
	}

	secretsData := make(map[string]interface{})

	if vaultd.kvVersion == 1 {
		secretsData = data
	} else if vaultd.kvVersion == 2 {
		secretsData["data"] = data
	}

	_, err = client.Logical().Write(secretsPath, secretsData)
	if err != nil {
		return err
	}

	return nil
}
