// Package e2ee provides end-to-end encryption operations for the MapleFile SDK.
package e2ee

import (
	"fmt"
)

// KeyChain holds the key encryption key derived from the user's password.
// It provides methods for decrypting keys in the E2EE chain.
type KeyChain struct {
	kek          []byte // Key Encryption Key derived from password
	salt         []byte // Password salt used for key derivation
	kdfAlgorithm string // KDF algorithm used ("argon2id" or "PBKDF2-SHA256")
}

// EncryptedKey represents a key encrypted with another key.
type EncryptedKey struct {
	Ciphertext []byte `json:"ciphertext"`
	Nonce      []byte `json:"nonce"`
}

// NewKeyChain creates a new KeyChain by deriving the KEK from the password and salt.
// This function defaults to Argon2id for backward compatibility.
// For cross-platform compatibility, use NewKeyChainWithAlgorithm instead.
func NewKeyChain(password string, salt []byte) (*KeyChain, error) {
	return NewKeyChainWithAlgorithm(password, salt, Argon2IDAlgorithm)
}

// NewKeyChainWithAlgorithm creates a new KeyChain using the specified KDF algorithm.
// algorithm should be one of: Argon2IDAlgorithm ("argon2id") or PBKDF2Algorithm ("PBKDF2-SHA256").
// The web frontend uses PBKDF2-SHA256, while the native app historically used Argon2id.
func NewKeyChainWithAlgorithm(password string, salt []byte, algorithm string) (*KeyChain, error) {
	// Validate salt size (both algorithms use 16-byte salt)
	if len(salt) != 16 {
		return nil, fmt.Errorf("invalid salt size: expected 16, got %d", len(salt))
	}

	// Derive key encryption key from password using specified algorithm
	kek, err := DeriveKeyFromPasswordWithAlgorithm(password, salt, algorithm)
	if err != nil {
		return nil, fmt.Errorf("failed to derive key from password: %w", err)
	}

	return &KeyChain{
		kek:          kek,
		salt:         salt,
		kdfAlgorithm: algorithm,
	}, nil
}

// Clear securely clears the KeyChain's sensitive data from memory.
// This should be called when the KeyChain is no longer needed.
func (k *KeyChain) Clear() {
	if k.kek != nil {
		ClearBytes(k.kek)
		k.kek = nil
	}
}

// DecryptMasterKey decrypts the user's master key using the KEK.
// This method auto-detects the cipher based on nonce size:
// - 12-byte nonce: ChaCha20-Poly1305 (native app)
// - 24-byte nonce: XSalsa20-Poly1305 (web frontend)
func (k *KeyChain) DecryptMasterKey(encryptedMasterKey *EncryptedKey) ([]byte, error) {
	if k.kek == nil {
		return nil, fmt.Errorf("keychain has been cleared")
	}

	// Auto-detect cipher based on nonce size
	masterKey, err := DecryptWithAlgorithm(encryptedMasterKey.Ciphertext, encryptedMasterKey.Nonce, k.kek)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt master key: %w", err)
	}

	return masterKey, nil
}

// DecryptCollectionKey decrypts a collection key using the master key.
// Auto-detects cipher based on nonce size (12 for ChaCha20, 24 for XSalsa20).
func DecryptCollectionKey(encryptedCollectionKey *EncryptedKey, masterKey []byte) ([]byte, error) {
	collectionKey, err := DecryptWithAlgorithm(encryptedCollectionKey.Ciphertext, encryptedCollectionKey.Nonce, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt collection key: %w", err)
	}

	return collectionKey, nil
}

// DecryptFileKey decrypts a file key using the collection key.
// Auto-detects cipher based on nonce size (12 for ChaCha20, 24 for XSalsa20).
func DecryptFileKey(encryptedFileKey *EncryptedKey, collectionKey []byte) ([]byte, error) {
	fileKey, err := DecryptWithAlgorithm(encryptedFileKey.Ciphertext, encryptedFileKey.Nonce, collectionKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt file key: %w", err)
	}

	return fileKey, nil
}

// DecryptPrivateKey decrypts the user's private key using the master key.
// Auto-detects cipher based on nonce size (12 for ChaCha20, 24 for XSalsa20).
func DecryptPrivateKey(encryptedPrivateKey *EncryptedKey, masterKey []byte) ([]byte, error) {
	privateKey, err := DecryptWithAlgorithm(encryptedPrivateKey.Ciphertext, encryptedPrivateKey.Nonce, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt private key: %w", err)
	}

	return privateKey, nil
}

// DecryptRecoveryKey decrypts the user's recovery key using the master key.
// Auto-detects cipher based on nonce size (12 for ChaCha20, 24 for XSalsa20).
func DecryptRecoveryKey(encryptedRecoveryKey *EncryptedKey, masterKey []byte) ([]byte, error) {
	recoveryKey, err := DecryptWithAlgorithm(encryptedRecoveryKey.Ciphertext, encryptedRecoveryKey.Nonce, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt recovery key: %w", err)
	}

	return recoveryKey, nil
}

// DecryptMasterKeyWithRecoveryKey decrypts the master key using the recovery key.
// This is used during account recovery.
// Auto-detects cipher based on nonce size (12 for ChaCha20, 24 for XSalsa20).
func DecryptMasterKeyWithRecoveryKey(encryptedMasterKey *EncryptedKey, recoveryKey []byte) ([]byte, error) {
	masterKey, err := DecryptWithAlgorithm(encryptedMasterKey.Ciphertext, encryptedMasterKey.Nonce, recoveryKey)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt master key with recovery key: %w", err)
	}

	return masterKey, nil
}

// GenerateMasterKey generates a new random master key.
func GenerateMasterKey() ([]byte, error) {
	return GenerateRandomBytes(MasterKeySize)
}

// GenerateCollectionKey generates a new random collection key.
func GenerateCollectionKey() ([]byte, error) {
	return GenerateRandomBytes(CollectionKeySize)
}

// GenerateFileKey generates a new random file key.
func GenerateFileKey() ([]byte, error) {
	return GenerateRandomBytes(FileKeySize)
}

// GenerateRecoveryKey generates a new random recovery key.
func GenerateRecoveryKey() ([]byte, error) {
	return GenerateRandomBytes(RecoveryKeySize)
}

// GenerateSalt generates a new random salt for password derivation.
func GenerateSalt() ([]byte, error) {
	return GenerateRandomBytes(Argon2SaltSize)
}

// EncryptMasterKey encrypts a master key with the KEK.
func (k *KeyChain) EncryptMasterKey(masterKey []byte) (*EncryptedKey, error) {
	if k.kek == nil {
		return nil, fmt.Errorf("keychain has been cleared")
	}

	encrypted, err := Encrypt(masterKey, k.kek)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptCollectionKey encrypts a collection key with the master key using ChaCha20-Poly1305.
// For web frontend compatibility, use EncryptCollectionKeySecretBox instead.
func EncryptCollectionKey(collectionKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(collectionKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt collection key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptCollectionKeySecretBox encrypts a collection key with the master key using XSalsa20-Poly1305.
// This is compatible with the web frontend's libsodium implementation.
func EncryptCollectionKeySecretBox(collectionKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(collectionKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt collection key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptFileKey encrypts a file key with the collection key.
// NOTE: This uses ChaCha20-Poly1305 (12-byte nonce). For web frontend compatibility,
// use EncryptFileKeySecretBox instead.
func EncryptFileKey(fileKey, collectionKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(fileKey, collectionKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt file key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptFileKeySecretBox encrypts a file key with the collection key using XSalsa20-Poly1305.
// This is compatible with the web frontend's libsodium implementation.
func EncryptFileKeySecretBox(fileKey, collectionKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(fileKey, collectionKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt file key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptPrivateKey encrypts a private key with the master key.
func EncryptPrivateKey(privateKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(privateKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt private key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptRecoveryKey encrypts a recovery key with the master key.
func EncryptRecoveryKey(recoveryKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(recoveryKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt recovery key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptMasterKeyWithRecoveryKey encrypts a master key with the recovery key.
// This is used to enable account recovery.
func EncryptMasterKeyWithRecoveryKey(masterKey, recoveryKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(masterKey, recoveryKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key with recovery key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// =============================================================================
// SecretBox (XSalsa20-Poly1305) Encryption Functions
// These match the web frontend's libsodium crypto_secretbox_easy implementation
// =============================================================================

// EncryptMasterKeySecretBox encrypts a master key with the KEK using XSalsa20-Poly1305.
// This is compatible with the web frontend's libsodium implementation.
func (k *KeyChain) EncryptMasterKeySecretBox(masterKey []byte) (*EncryptedKey, error) {
	if k.kek == nil {
		return nil, fmt.Errorf("keychain has been cleared")
	}

	encrypted, err := EncryptWithSecretBox(masterKey, k.kek)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptPrivateKeySecretBox encrypts a private key with the master key using XSalsa20-Poly1305.
func EncryptPrivateKeySecretBox(privateKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(privateKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt private key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptRecoveryKeySecretBox encrypts a recovery key with the master key using XSalsa20-Poly1305.
func EncryptRecoveryKeySecretBox(recoveryKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(recoveryKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt recovery key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptMasterKeyWithRecoveryKeySecretBox encrypts a master key with the recovery key using XSalsa20-Poly1305.
func EncryptMasterKeyWithRecoveryKeySecretBox(masterKey, recoveryKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(masterKey, recoveryKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key with recovery key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptCollectionKeyForSharing encrypts a collection key for a recipient using BoxSeal.
// This is used when sharing a collection with another user.
func EncryptCollectionKeyForSharing(collectionKey, recipientPublicKey []byte) ([]byte, error) {
	if len(recipientPublicKey) != BoxPublicKeySize {
		return nil, fmt.Errorf("invalid recipient public key size: expected %d, got %d", BoxPublicKeySize, len(recipientPublicKey))
	}

	return EncryptWithBoxSeal(collectionKey, recipientPublicKey)
}

// DecryptSharedCollectionKey decrypts a collection key that was shared using BoxSeal.
// This is used when accessing a shared collection.
func DecryptSharedCollectionKey(encryptedCollectionKey, publicKey, privateKey []byte) ([]byte, error) {
	return DecryptWithBoxSeal(encryptedCollectionKey, publicKey, privateKey)
}

// ============================================================================
// Tag Key Operations
// ============================================================================

// GenerateTagKey generates a new 32-byte tag key for encrypting tag data.
func GenerateTagKey() ([]byte, error) {
	return GenerateRandomBytes(SecretBoxKeySize)
}

// GenerateKey is an alias for GenerateTagKey (convenience function).
func GenerateKey() []byte {
	key, _ := GenerateTagKey()
	return key
}

// EncryptTagKey encrypts a tag key with the master key using ChaCha20-Poly1305.
func EncryptTagKey(tagKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := Encrypt(tagKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// EncryptTagKeySecretBox encrypts a tag key with the master key using XSalsa20-Poly1305.
func EncryptTagKeySecretBox(tagKey, masterKey []byte) (*EncryptedKey, error) {
	encrypted, err := EncryptWithSecretBox(tagKey, masterKey)
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt tag key: %w", err)
	}

	return &EncryptedKey{
		Ciphertext: encrypted.Ciphertext,
		Nonce:      encrypted.Nonce,
	}, nil
}

// DecryptTagKey decrypts a tag key with the master key.
func DecryptTagKey(encryptedTagKey *EncryptedKey, masterKey []byte) ([]byte, error) {
	// Try XSalsa20-Poly1305 first (based on nonce size)
	if len(encryptedTagKey.Nonce) == SecretBoxNonceSize {
		return DecryptWithSecretBox(encryptedTagKey.Ciphertext, encryptedTagKey.Nonce, masterKey)
	}

	// Fall back to ChaCha20-Poly1305
	return Decrypt(encryptedTagKey.Ciphertext, encryptedTagKey.Nonce, masterKey)
}
