// Package crypto provides tenant-scoped encryption key management for multi-tenant deployments.
// This implements Phase 9.4: Per-Tenant Encryption Keys.
package crypto

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"strings"
	"time"
)

// KeyHierarchy defines the tenant key structure
// Root Key (per tenant) -> Data Encryption Keys (per artifact)
type KeyHierarchy struct {
	TenantID   string    `json:"tenant_id"`
	RootKeyID  string    `json:"root_key_id"`
	CreatedAt  time.Time `json:"created_at"`
	Algorithm  string    `json:"algorithm"` // Always "AES-256-GCM"
}

// TenantKeyManager manages per-tenant encryption keys
// In production, root keys should be stored in a KMS (HashiCorp Vault, AWS KMS, etc.)
type TenantKeyManager struct {
	// In-memory store for development; use external KMS in production
	rootKeys map[string][]byte // tenantID -> root key
}

// NewTenantKeyManager creates a new tenant key manager
func NewTenantKeyManager() *TenantKeyManager {
	return &TenantKeyManager{
		rootKeys: make(map[string][]byte),
	}
}

// ProvisionTenant creates a new root key for a tenant
// In production, this would call out to a KMS to create a key
func (km *TenantKeyManager) ProvisionTenant(tenantID string) (*KeyHierarchy, error) {
	if strings.TrimSpace(tenantID) == "" {
		return nil, fmt.Errorf("tenant ID cannot be empty")
	}

	// Generate root key (32 bytes for AES-256)
	rootKey := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, rootKey); err != nil {
		return nil, fmt.Errorf("failed to generate root key: %w", err)
	}

	// Create key ID from hash of key (for reference, not for key derivation)
	h := sha256.Sum256(rootKey)
	rootKeyID := hex.EncodeToString(h[:8]) // First 8 bytes as ID

	// Store root key
	km.rootKeys[tenantID] = rootKey

	return &KeyHierarchy{
		TenantID:  tenantID,
		RootKeyID: rootKeyID,
		CreatedAt: time.Now().UTC(),
		Algorithm: "AES-256-GCM",
	}, nil
}

// RotateTenantKey rotates the root key for a tenant
// Existing data must be re-encrypted with the new key
func (km *TenantKeyManager) RotateTenantKey(tenantID string) (*KeyHierarchy, error) {
	// Delete old key
	delete(km.rootKeys, tenantID)

	// Provision new key
	return km.ProvisionTenant(tenantID)
}

// RevokeTenant removes all keys for a tenant
// This effectively makes all encrypted data inaccessible
func (km *TenantKeyManager) RevokeTenant(tenantID string) error {
	if _, exists := km.rootKeys[tenantID]; !exists {
		return fmt.Errorf("tenant %s not found", tenantID)
	}

	// Overwrite key before deleting (best effort)
	key := km.rootKeys[tenantID]
	for i := range key {
		key[i] = 0
	}
	delete(km.rootKeys, tenantID)

	return nil
}

// GenerateDataEncryptionKey creates a unique DEK for an artifact
// The DEK is wrapped (encrypted) under the tenant's root key
func (km *TenantKeyManager) GenerateDataEncryptionKey(tenantID string, artifactID string) (*WrappedDEK, error) {
	rootKey, exists := km.rootKeys[tenantID]
	if !exists {
		return nil, fmt.Errorf("no root key found for tenant %s", tenantID)
	}

	// Generate unique DEK (32 bytes for AES-256)
	dek := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, dek); err != nil {
		return nil, fmt.Errorf("failed to generate DEK: %w", err)
	}

	// Wrap DEK with root key
	wrappedKey, err := km.wrapKey(rootKey, dek)
	if err != nil {
		return nil, fmt.Errorf("failed to wrap DEK: %w", err)
	}

	// Clear plaintext DEK from memory
	for i := range dek {
		dek[i] = 0
	}

	return &WrappedDEK{
		TenantID:    tenantID,
		ArtifactID:  artifactID,
		WrappedKey:  wrappedKey,
		Algorithm:   "AES-256-GCM",
		CreatedAt:   time.Now().UTC(),
	}, nil
}

// UnwrapDataEncryptionKey decrypts a wrapped DEK using the tenant's root key
func (km *TenantKeyManager) UnwrapDataEncryptionKey(wrappedDEK *WrappedDEK) ([]byte, error) {
	rootKey, exists := km.rootKeys[wrappedDEK.TenantID]
	if !exists {
		return nil, fmt.Errorf("no root key found for tenant %s", wrappedDEK.TenantID)
	}

	return km.unwrapKey(rootKey, wrappedDEK.WrappedKey)
}

// WrappedDEK represents a data encryption key wrapped under a tenant root key
type WrappedDEK struct {
	TenantID   string    `json:"tenant_id"`
	ArtifactID string    `json:"artifact_id"`
	WrappedKey string    `json:"wrapped_key"` // base64 encoded
	Algorithm  string    `json:"algorithm"`
	CreatedAt  time.Time `json:"created_at"`
}

// wrapKey encrypts a key using AES-256-GCM with the provided root key
func (km *TenantKeyManager) wrapKey(rootKey, keyToWrap []byte) (string, error) {
	block, err := aes.NewCipher(rootKey)
	if err != nil {
		return "", fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", fmt.Errorf("failed to create GCM: %w", err)
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", fmt.Errorf("failed to generate nonce: %w", err)
	}

	ciphertext := gcm.Seal(nonce, nonce, keyToWrap, nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// unwrapKey decrypts a wrapped key using AES-256-GCM
func (km *TenantKeyManager) unwrapKey(rootKey []byte, wrappedKey string) ([]byte, error) {
	ciphertext, err := base64.StdEncoding.DecodeString(wrappedKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decode wrapped key: %w", err)
	}

	block, err := aes.NewCipher(rootKey)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, fmt.Errorf("ciphertext too short")
	}

	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
	return gcm.Open(nil, nonce, ciphertext, nil)
}

// EncryptArtifact encrypts artifact data using a tenant-specific DEK
func (km *TenantKeyManager) EncryptArtifact(tenantID string, artifactID string, plaintext []byte) (*EncryptedArtifact, error) {
	// Generate a new DEK for this artifact
	wrappedDEK, err := km.GenerateDataEncryptionKey(tenantID, artifactID)
	if err != nil {
		return nil, err
	}

	// Unwrap the DEK for use
	dek, err := km.UnwrapDataEncryptionKey(wrappedDEK)
	if err != nil {
		return nil, err
	}
	defer func() {
		// Clear DEK from memory after use
		for i := range dek {
			dek[i] = 0
		}
	}()

	// Encrypt the data with the DEK
	block, err := aes.NewCipher(dek)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, fmt.Errorf("failed to generate nonce: %w", err)
	}

	ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)

	return &EncryptedArtifact{
		Ciphertext: base64.StdEncoding.EncodeToString(ciphertext),
		DEK:        wrappedDEK,
		Algorithm:  "AES-256-GCM",
	}, nil
}

// DecryptArtifact decrypts artifact data using its wrapped DEK
func (km *TenantKeyManager) DecryptArtifact(encrypted *EncryptedArtifact) ([]byte, error) {
	// Unwrap the DEK
	dek, err := km.UnwrapDataEncryptionKey(encrypted.DEK)
	if err != nil {
		return nil, fmt.Errorf("failed to unwrap DEK: %w", err)
	}
	defer func() {
		for i := range dek {
			dek[i] = 0
		}
	}()

	// Decrypt the data
	ciphertext, err := base64.StdEncoding.DecodeString(encrypted.Ciphertext)
	if err != nil {
		return nil, fmt.Errorf("failed to decode ciphertext: %w", err)
	}

	block, err := aes.NewCipher(dek)
	if err != nil {
		return nil, fmt.Errorf("failed to create cipher: %w", err)
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("failed to create GCM: %w", err)
	}

	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return nil, fmt.Errorf("ciphertext too short")
	}

	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
	return gcm.Open(nil, nonce, ciphertext, nil)
}

// EncryptedArtifact represents an encrypted artifact with its wrapped DEK
type EncryptedArtifact struct {
	Ciphertext string       `json:"ciphertext"` // base64 encoded
	DEK        *WrappedDEK  `json:"dek"`
	Algorithm  string       `json:"algorithm"`
}

// AuditLogEntry represents an audit log entry for encryption/decryption operations
type AuditLogEntry struct {
	Timestamp  time.Time `json:"timestamp"`
	Operation  string    `json:"operation"` // "encrypt", "decrypt", "key_rotation"
	TenantID   string    `json:"tenant_id"`
	ArtifactID string    `json:"artifact_id,omitempty"`
	KeyID      string    `json:"key_id"`
	Success    bool      `json:"success"`
	Error      string    `json:"error,omitempty"`
}
