// Package secrets abstracts secret retrieval across backends (SOPS, Infisical).
// Phase 5: SOPS → Infisical migration with SOPS DR fallback.
package secrets

import (
	"context"
	"errors"
	"sync"
	"time"
)

var ErrNotFound = errors.New("secret not found")
var ErrBackendUnavailable = errors.New("secret backend unavailable")

// Backend is the interface for retrieving and storing secrets.
type Backend interface {
	// Get retrieves a secret value by path/key.
	Get(ctx context.Context, key string) (string, error)
	// List returns all secret keys available in this backend.
	List(ctx context.Context) ([]string, error)
	// Set stores a secret value. Used during migration.
	Set(ctx context.Context, key string, value string) error
	// Name returns a human-readable backend identifier.
	Name() string
}

// Manager holds a primary and fallback backend. If the primary fails,
// it falls back to the secondary.
type Manager struct {
	primary   Backend
	fallback  Backend
	cache     map[string]cachedSecret
	mu        sync.RWMutex
	cacheTTL  time.Duration
}

type cachedSecret struct {
	value     string
	expiresAt time.Time
}

// NewManager creates a secret manager with primary and fallback backends.
func NewManager(primary, fallback Backend) *Manager {
	return &Manager{
		primary:  primary,
		fallback: fallback,
		cache:    make(map[string]cachedSecret),
		cacheTTL: 5 * time.Minute,
	}
}

// Get retrieves a secret from primary, falling back to secondary on error.
func (m *Manager) Get(ctx context.Context, key string) (string, error) {
	m.mu.RLock()
	if cached, ok := m.cache[key]; ok && time.Now().Before(cached.expiresAt) {
		m.mu.RUnlock()
		return cached.value, nil
	}
	m.mu.RUnlock()

	val, err := m.primary.Get(ctx, key)
	if err == nil {
		m.mu.Lock()
		m.cache[key] = cachedSecret{value: val, expiresAt: time.Now().Add(m.cacheTTL)}
		m.mu.Unlock()
		return val, nil
	}

	if m.fallback != nil {
		val, fallbackErr := m.fallback.Get(ctx, key)
		if fallbackErr == nil {
			return val, nil
		}
	}

	return "", err
}

// List returns all keys from the primary backend.
func (m *Manager) List(ctx context.Context) ([]string, error) {
	keys, err := m.primary.List(ctx)
	if err != nil && m.fallback != nil {
		return m.fallback.List(ctx)
	}
	return keys, err
}

// Set stores a secret in the primary backend (used during migration).
func (m *Manager) Set(ctx context.Context, key string, value string) error {
	return m.primary.Set(ctx, key, value)
}

// PrimaryName returns the name of the primary backend.
func (m *Manager) PrimaryName() string {
	return m.primary.Name()
}

// InvalidateCache clears cached secrets.
func (m *Manager) InvalidateCache() {
	m.mu.Lock()
	m.cache = make(map[string]cachedSecret)
	m.mu.Unlock()
}
