package crypto

import (
	"context"
	"encoding/hex"
	"fmt"
	"os"
	"sync"
)

// Provider resolves the raw 32-byte AES-256 key material for a given key
// version. The key material itself never lives in the DB — only the version
// number (in crypto_keys + the per-row key_version column) is persisted; the
// provider maps that version back to actual bytes (env var, Vault, KMS, ...).
type Provider interface {
	// KeyMaterial returns the 32-byte key for the given version, or an error if
	// the version is unknown / unavailable. Implementations may cache.
	KeyMaterial(ctx context.Context, version int) ([]byte, error)
}

// EnvProvider maps key versions to env vars. Version 1 is APP_MASTER_KEY
// (the historical single key); higher versions read APP_MASTER_KEY_V<n> so a
// rotation can stage the next key alongside the current one. The constructor
// also accepts an explicit version-1 key so callers that already decoded
// cfg.MasterKey do not re-read the env.
type EnvProvider struct {
	mu   sync.RWMutex
	keys map[int][]byte
}

// NewEnvProvider builds an EnvProvider seeded with the version-1 key (typically
// the already-decoded cfg.MasterKey). A nil/empty v1 key falls back to reading
// APP_MASTER_KEY lazily on first KeyMaterial(1).
func NewEnvProvider(v1 []byte) *EnvProvider {
	p := &EnvProvider{keys: map[int][]byte{}}
	if len(v1) > 0 {
		p.keys[1] = append([]byte(nil), v1...)
	}
	return p
}

// KeyMaterial returns the cached key for version, loading it from the matching
// env var on first use. Version 1 → APP_MASTER_KEY; version n>1 →
// APP_MASTER_KEY_V<n>. The value must be 64 hex chars (32 bytes).
func (p *EnvProvider) KeyMaterial(_ context.Context, version int) ([]byte, error) {
	if version < 1 {
		return nil, fmt.Errorf("crypto: invalid key version %d", version)
	}
	p.mu.RLock()
	k, ok := p.keys[version]
	p.mu.RUnlock()
	if ok {
		return k, nil
	}

	envName := "APP_MASTER_KEY"
	if version > 1 {
		envName = fmt.Sprintf("APP_MASTER_KEY_V%d", version)
	}
	raw := os.Getenv(envName)
	if raw == "" {
		return nil, fmt.Errorf("crypto: key version %d unavailable (%s unset)", version, envName)
	}
	key, err := hex.DecodeString(raw)
	if err != nil {
		return nil, fmt.Errorf("crypto: %s invalid hex: %w", envName, err)
	}
	if len(key) != 32 {
		return nil, fmt.Errorf("crypto: %s must be 32 bytes (got %d)", envName, len(key))
	}
	p.mu.Lock()
	p.keys[version] = key
	p.mu.Unlock()
	return key, nil
}

// SetKey installs key material for a version explicitly (used in tests and when
// a KMS-fetched key is handed to an EnvProvider-style cache).
func (p *EnvProvider) SetKey(version int, key []byte) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.keys[version] = append([]byte(nil), key...)
}

// VaultProvider / KMSProvider are deferred: their interface is identical to
// EnvProvider (KeyMaterial(ctx, version) -> []byte), so wiring a real
// HashiCorp Vault transit / cloud KMS client is a drop-in replacement. They are
// declared here so config.Provider can select "vault"/"kms" without a build
// break; until a real client is wired they delegate to a backing Provider.

// DelegatingProvider wraps another Provider; it is the shape both VaultProvider
// and KMSProvider take until their remote clients are implemented.
type DelegatingProvider struct{ inner Provider }

// NewDelegatingProvider returns a Provider that forwards to inner.
func NewDelegatingProvider(inner Provider) *DelegatingProvider {
	return &DelegatingProvider{inner: inner}
}

// KeyMaterial forwards to the wrapped provider.
func (d *DelegatingProvider) KeyMaterial(ctx context.Context, version int) ([]byte, error) {
	return d.inner.KeyMaterial(ctx, version)
}
