/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

/*
Package setup provides functions to create a KMS and key store from a given URI.

This package does not provide any functionality to interact with the KMS or key store,
but only to create them.

Adding support for a new KMS or storage backend requires adding a new URI for that backend,
and implementing the corresponding get*Config function.
*/
package setup

import (
	"context"
	"fmt"
	"net/url"

	"github.com/edgelesssys/constellation/v2/internal/kms/kms"
	"github.com/edgelesssys/constellation/v2/internal/kms/kms/aws"
	"github.com/edgelesssys/constellation/v2/internal/kms/kms/azure"
	"github.com/edgelesssys/constellation/v2/internal/kms/kms/cluster"
	"github.com/edgelesssys/constellation/v2/internal/kms/kms/gcp"
	"github.com/edgelesssys/constellation/v2/internal/kms/storage/awss3"
	"github.com/edgelesssys/constellation/v2/internal/kms/storage/azureblob"
	"github.com/edgelesssys/constellation/v2/internal/kms/storage/gcs"
	"github.com/edgelesssys/constellation/v2/internal/kms/uri"
)

// KMSInformation about an existing KMS.
type KMSInformation struct {
	KMSURI             string
	StorageURI         string
	KeyEncryptionKeyID string
}

// KMS creates a KMS and key store from the given parameters.
func KMS(ctx context.Context, storageURI, kmsURI string) (kms.CloudKMS, error) {
	store, err := getStore(ctx, storageURI)
	if err != nil {
		return nil, err
	}

	return getKMS(ctx, kmsURI, store)
}

// getStore creates a key store depending on the given parameters.
func getStore(ctx context.Context, storageURI string) (kms.Storage, error) {
	url, err := url.Parse(storageURI)
	if err != nil {
		return nil, err
	}
	if url.Scheme != "storage" {
		return nil, fmt.Errorf("invalid storage URI: invalid scheme: %s", url.Scheme)
	}

	switch url.Host {
	case "aws":
		cfg, err := uri.DecodeAWSS3ConfigFromURI(storageURI)
		if err != nil {
			return nil, err
		}
		return awss3.New(ctx, cfg)

	case "azure":
		cfg, err := uri.DecodeAzureBlobConfigFromURI(storageURI)
		if err != nil {
			return nil, err
		}
		return azureblob.New(ctx, cfg)

	case "gcp":
		cfg, err := uri.DecodeGoogleCloudStorageConfigFromURI(storageURI)
		if err != nil {
			return nil, err
		}
		return gcs.New(ctx, cfg)

	case "no-store":
		return nil, nil

	default:
		return nil, fmt.Errorf("unknown storage type: %s", url.Host)
	}
}

// getKMS creates a KMS client with the given key store and depending on the given parameters.
func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS, error) {
	url, err := url.Parse(kmsURI)
	if err != nil {
		return nil, err
	}
	if url.Scheme != "kms" {
		return nil, fmt.Errorf("invalid KMS URI: invalid scheme: %s", url.Scheme)
	}

	switch url.Host {
	case "aws":
		cfg, err := uri.DecodeAWSConfigFromURI(kmsURI)
		if err != nil {
			return nil, fmt.Errorf("invalid AWS KMS URI: %w", err)
		}
		return aws.New(ctx, store, cfg)

	case "azure":
		cfg, err := uri.DecodeAzureConfigFromURI(kmsURI)
		if err != nil {
			return nil, fmt.Errorf("invalid Azure Key Vault URI: %w", err)
		}
		return azure.New(ctx, store, cfg)

	case "gcp":
		cfg, err := uri.DecodeGCPConfigFromURI(kmsURI)
		if err != nil {
			return nil, fmt.Errorf("invalid GCP KMS URI: %w", err)
		}
		return gcp.New(ctx, store, cfg)

	case "cluster-kms":
		cfg, err := uri.DecodeMasterSecretFromURI(kmsURI)
		if err != nil {
			return nil, err
		}
		return cluster.New(cfg.Key, cfg.Salt)

	default:
		return nil, fmt.Errorf("unknown KMS type: %s", url.Host)
	}
}