package credentialstore

import (
	"context"
	"errors"
	"fmt"
	"strings"
)

var (
	// ErrEnvelopeKeyUnavailable is returned when the key registry cannot be reached or is unconfigured.
	ErrEnvelopeKeyUnavailable = errors.New("credentialstore: envelope key unavailable")

	// ErrUnknownEnvelopeKey is returned when the referenced key ID or version is not registered.
	ErrUnknownEnvelopeKey = errors.New("credentialstore: unknown envelope key")

	// ErrInvalidSecretEnvelope is returned when envelope fields fail validation.
	ErrInvalidSecretEnvelope = errors.New("credentialstore: invalid secret envelope")
)

// EnvelopeKeyRegistry checks whether an envelope encryption key is registered.
type EnvelopeKeyRegistry interface {
	HasEnvelopeKey(ctx context.Context, keyID string, keyVersion uint64) (bool, error)
}

const (
	// algorithmAES256GCM is the only authenticated-encryption algorithm the
	// store accepts. It is declared locally so credentialstore never depends on
	// the higher-layer credentialseal package.
	algorithmAES256GCM = "AES-256-GCM"

	// minNonceLength is the minimum AES-GCM nonce length (the 96-bit standard).
	// Shorter nonces weaken GCM and are rejected at the persistence boundary.
	minNonceLength = 12
)

type unavailableKeyRegistry struct{}

func (unavailableKeyRegistry) HasEnvelopeKey(ctx context.Context, keyID string, keyVersion uint64) (bool, error) {
	return false, ErrEnvelopeKeyUnavailable
}

// SecretEnvelope holds opaque encrypted secret payload and key metadata.
type SecretEnvelope struct {
	Algorithm  string `json:"algorithm"`
	KeyID      string `json:"key_id"`
	KeyVersion uint64 `json:"key_version"`
	Nonce      []byte `json:"nonce"`
	Ciphertext []byte `json:"ciphertext"`
	AAD        []byte `json:"aad,omitempty"`
}

// Validate checks that the envelope carries a supported authenticated-encryption
// algorithm, a registered-shaped key revision, a full-length nonce, and
// ciphertext. It stays secret-blind: it inspects only opaque metadata and never
// the plaintext. AAD is optional metadata and is not required to be present.
func (e SecretEnvelope) Validate() error {
	algorithm := strings.TrimSpace(e.Algorithm)
	if algorithm == "" {
		return fmt.Errorf("%w: algorithm is required", ErrInvalidSecretEnvelope)
	}
	if algorithm != algorithmAES256GCM {
		return fmt.Errorf("%w: unsupported algorithm", ErrInvalidSecretEnvelope)
	}
	if strings.TrimSpace(e.KeyID) == "" {
		return fmt.Errorf("%w: key_id is required", ErrInvalidSecretEnvelope)
	}
	if e.KeyVersion == 0 {
		return fmt.Errorf("%w: key_version must be greater than 0", ErrInvalidSecretEnvelope)
	}
	if len(e.Nonce) < minNonceLength {
		return fmt.Errorf("%w: nonce is too short", ErrInvalidSecretEnvelope)
	}
	if len(e.Ciphertext) == 0 {
		return fmt.Errorf("%w: ciphertext is required", ErrInvalidSecretEnvelope)
	}
	return nil
}

// validateEnvelopeKey checks that the envelope is valid and its key ID and version are registered.
func (s *Store) validateEnvelopeKey(ctx context.Context, env SecretEnvelope) error {
	if err := env.Validate(); err != nil {
		return err
	}
	registry := s.KeyRegistry()
	ok, err := registry.HasEnvelopeKey(ctx, env.KeyID, env.KeyVersion)
	if err != nil {
		if errors.Is(err, ErrEnvelopeKeyUnavailable) {
			return err
		}
		return fmt.Errorf("%w: %v", ErrEnvelopeKeyUnavailable, err)
	}
	if !ok {
		return fmt.Errorf("%w: key_id=%s version=%d", ErrUnknownEnvelopeKey, env.KeyID, env.KeyVersion)
	}
	return nil
}
