package app

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"time"

	"github.com/tyler-smith/go-bip39"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/client"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/maplefile/e2ee"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/inputvalidation"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/service/ratelimiter"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
)

// RequestOTT requests a one-time token for login
func (a *Application) RequestOTT(email string) error {
	// Validate input
	if err := inputvalidation.ValidateEmail(email); err != nil {
		return err
	}

	// Check rate limit before making request
	// Note: We do NOT reset on success here - the rate limit prevents spamming
	// the "request OTT" button. Users should wait between OTT requests.
	if err := a.rateLimiter.Check(ratelimiter.OpRequestOTT, email); err != nil {
		a.logger.Warn("OTT request rate limited",
			zap.String("email", utils.MaskEmail(email)),
			zap.Error(err))
		return err
	}

	return a.authService.RequestOTT(a.ctx, email)
}

// Logout logs out the current user and deletes all local data (default behavior for security).
// Use LogoutWithOptions for more control over local data deletion.
func (a *Application) Logout() error {
	return a.LogoutWithOptions(true) // Default to deleting local data for security
}

// LogoutWithOptions logs out the current user with control over local data deletion.
// If deleteLocalData is true, all locally cached files and metadata will be permanently deleted.
// If deleteLocalData is false, local data is preserved for faster login next time.
func (a *Application) LogoutWithOptions(deleteLocalData bool) error {
	// Get session before clearing
	session, _ := a.authService.GetCurrentSession(a.ctx)
	var userEmail string
	if session != nil {
		userEmail = session.Email
	}

	// Stop token manager first
	stopCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	if err := a.tokenManager.Stop(stopCtx); err != nil {
		a.logger.Error("Failed to stop token manager during logout", zap.Error(err))
		// Continue with logout even if token manager stop failed
	}

	// Clear stored password from RAM
	if session != nil {
		if err := a.passwordStore.ClearPassword(session.Email); err != nil {
			a.logger.Error("Failed to clear stored password", zap.Error(err))
		} else {
			a.logger.Info("Password cleared from secure RAM", zap.String("email", utils.MaskEmail(session.Email)))
		}

		// Clear cached master key from memory (if it exists)
		if a.keyCache.HasMasterKey(session.Email) {
			if err := a.keyCache.ClearMasterKey(session.Email); err != nil {
				a.logger.Warn("Failed to clear cached master key", zap.Error(err))
			} else {
				a.logger.Info("Cached master key cleared from secure memory", zap.String("email", utils.MaskEmail(session.Email)))
			}
		} else {
			a.logger.Debug("No cached master key to clear (expected after app restart)", zap.String("email", utils.MaskEmail(session.Email)))
		}
	}

	// Close search index
	if err := a.searchService.Close(); err != nil {
		a.logger.Error("Failed to close search index during logout", zap.Error(err))
		// Continue with logout even if search cleanup fails
	} else {
		a.logger.Info("Search index closed")
	}

	// Handle local data based on user preference
	if deleteLocalData && userEmail != "" {
		// Delete all local data permanently
		if err := a.storageManager.DeleteUserData(userEmail); err != nil {
			a.logger.Error("Failed to delete local user data", zap.Error(err))
			// Continue with logout even if deletion fails
		} else {
			a.logger.Info("All local user data deleted", zap.String("email", utils.MaskEmail(userEmail)))
		}
	} else {
		// Just cleanup storage connections (keep data on disk)
		a.storageManager.Cleanup()
		a.logger.Info("User storage connections closed, local data preserved")
	}

	// Clear session
	return a.authService.Logout(a.ctx)
}

// GetLocalDataSize returns the size of locally stored data for the current user in bytes.
// This can be used to show the user how much data will be deleted on logout.
func (a *Application) GetLocalDataSize() (int64, error) {
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err != nil || session == nil {
		return 0, nil
	}

	size, err := a.storageManager.GetUserDataSize(session.Email)
	if err != nil {
		a.logger.Warn("Failed to get local data size", zap.Error(err))
		return 0, err
	}

	return size, nil
}

// IsLoggedIn checks if a user is logged in
func (a *Application) IsLoggedIn() (bool, error) {
	return a.authService.IsLoggedIn(a.ctx)
}

// Register creates a new user account
func (a *Application) Register(input *client.RegisterInput) error {
	// Validate input
	if err := inputvalidation.ValidateEmail(input.Email); err != nil {
		return err
	}
	if err := inputvalidation.ValidateDisplayName(input.FirstName, "first name"); err != nil {
		return err
	}
	if err := inputvalidation.ValidateDisplayName(input.LastName, "last name"); err != nil {
		return err
	}
	// Note: Password is not sent directly in RegisterInput - it's used client-side
	// to derive encryption keys. The encrypted master key and salt are validated
	// by their presence and format on the server side.

	// Check rate limit before making request
	// Note: We do NOT reset on success - registration is a one-time operation
	// and keeping the rate limit prevents re-registration spam attempts.
	if err := a.rateLimiter.Check(ratelimiter.OpRegister, input.Email); err != nil {
		a.logger.Warn("Registration rate limited",
			zap.String("email", utils.MaskEmail(input.Email)),
			zap.Error(err))
		return err
	}

	return a.authService.Register(a.ctx, input)
}

// VerifyEmail verifies the email with the verification code
func (a *Application) VerifyEmail(email, code string) error {
	// Validate input
	if err := inputvalidation.ValidateEmail(email); err != nil {
		return err
	}
	if err := inputvalidation.ValidateOTT(code); err != nil {
		return err
	}

	// Check rate limit before making request
	if err := a.rateLimiter.Check(ratelimiter.OpVerifyEmail, email); err != nil {
		a.logger.Warn("Email verification rate limited",
			zap.String("email", utils.MaskEmail(email)),
			zap.Error(err))
		return err
	}

	input := &client.VerifyEmailInput{
		Email: email,
		Code:  code,
	}
	err := a.authService.VerifyEmail(a.ctx, input)
	if err == nil {
		// Reset rate limit on success
		a.rateLimiter.Reset(ratelimiter.OpVerifyEmail, email)
	}
	return err
}

// VerifyOTTResponse contains the OTT verification response with encrypted challenge
type VerifyOTTResponse struct {
	Message             string `json:"message"`
	ChallengeID         string `json:"challengeId"`
	EncryptedChallenge  string `json:"encryptedChallenge"`
	Salt                string `json:"salt"`
	EncryptedMasterKey  string `json:"encryptedMasterKey"`
	EncryptedPrivateKey string `json:"encryptedPrivateKey"`
	PublicKey           string `json:"publicKey"`
	// KDFAlgorithm specifies which key derivation algorithm to use.
	// Value: "PBKDF2-SHA256"
	KDFAlgorithm string `json:"kdfAlgorithm"`
}

// VerifyOTT verifies the one-time token and returns the encrypted challenge
func (a *Application) VerifyOTT(email, ott string) (*VerifyOTTResponse, error) {
	// Validate input
	if err := inputvalidation.ValidateEmail(email); err != nil {
		return nil, err
	}
	if err := inputvalidation.ValidateOTT(ott); err != nil {
		return nil, err
	}

	// Check rate limit before making request
	if err := a.rateLimiter.Check(ratelimiter.OpVerifyOTT, email); err != nil {
		a.logger.Warn("OTT verification rate limited",
			zap.String("email", utils.MaskEmail(email)),
			zap.Error(err))
		return nil, err
	}

	resp, err := a.authService.VerifyOTT(a.ctx, email, ott)
	if err != nil {
		a.logger.Error("OTT verification failed", zap.Error(err))
		return nil, err
	}

	// Reset rate limit on success
	a.rateLimiter.Reset(ratelimiter.OpVerifyOTT, email)

	// Get KDF algorithm from response, default to PBKDF2-SHA256
	kdfAlgorithm := resp.KDFAlgorithm
	if kdfAlgorithm == "" {
		kdfAlgorithm = e2ee.PBKDF2Algorithm
	}

	return &VerifyOTTResponse{
		Message:             resp.Message,
		ChallengeID:         resp.ChallengeID,
		EncryptedChallenge:  resp.EncryptedChallenge,
		Salt:                resp.Salt,
		EncryptedMasterKey:  resp.EncryptedMasterKey,
		EncryptedPrivateKey: resp.EncryptedPrivateKey,
		PublicKey:           resp.PublicKey,
		KDFAlgorithm:        kdfAlgorithm,
	}, nil
}

// CompleteLoginInput contains the data needed to complete login
type CompleteLoginInput struct {
	Email         string `json:"email"`
	ChallengeID   string `json:"challengeId"`
	DecryptedData string `json:"decryptedData"`
	Password      string `json:"password"`

	// Encrypted user data for future password verification
	Salt                string `json:"salt"`
	EncryptedMasterKey  string `json:"encryptedMasterKey"`
	EncryptedPrivateKey string `json:"encryptedPrivateKey"`
	PublicKey           string `json:"publicKey"`

	// KDFAlgorithm specifies which key derivation algorithm to use.
	// Value: "PBKDF2-SHA256"
	KDFAlgorithm string `json:"kdfAlgorithm"`
}

// CompleteLogin completes the login process with the decrypted challenge
func (a *Application) CompleteLogin(input *CompleteLoginInput) error {
	// Validate input
	if err := inputvalidation.ValidateEmail(input.Email); err != nil {
		return err
	}
	if err := inputvalidation.ValidatePassword(input.Password); err != nil {
		return err
	}
	if input.ChallengeID == "" {
		return fmt.Errorf("challenge ID is required")
	}
	if input.DecryptedData == "" {
		return fmt.Errorf("decrypted data is required")
	}

	// Check rate limit before making request
	if err := a.rateLimiter.Check(ratelimiter.OpCompleteLogin, input.Email); err != nil {
		a.logger.Warn("Login completion rate limited",
			zap.String("email", utils.MaskEmail(input.Email)),
			zap.Error(err))
		return err
	}

	clientInput := &client.CompleteLoginInput{
		Email:         input.Email,
		ChallengeID:   input.ChallengeID,
		DecryptedData: input.DecryptedData,
	}

	_, err := a.authService.CompleteLogin(a.ctx, clientInput)
	if err != nil {
		a.logger.Error("Login completion failed", zap.Error(err))
		return err
	}

	// Reset all rate limits for this user on successful login
	a.rateLimiter.ResetAll(input.Email)

	// Store encrypted user data in session for future password verification
	session, err := a.authService.GetCurrentSession(a.ctx)
	if err == nil && session != nil {
		session.Salt = input.Salt
		session.EncryptedMasterKey = input.EncryptedMasterKey
		session.EncryptedPrivateKey = input.EncryptedPrivateKey
		session.PublicKey = input.PublicKey
		// Store KDF algorithm so VerifyPassword knows which algorithm to use
		session.KDFAlgorithm = input.KDFAlgorithm
		if session.KDFAlgorithm == "" {
			session.KDFAlgorithm = e2ee.PBKDF2Algorithm
		}

		// Update session with encrypted data
		if err := a.authService.UpdateSession(a.ctx, session); err != nil {
			a.logger.Warn("Failed to update session with encrypted data", zap.Error(err))
			// Continue anyway - password storage will still work
		} else {
			a.logger.Info("Encrypted user data stored in session for password verification")
		}
	}

	// Store password in secure RAM
	if err := a.passwordStore.StorePassword(input.Email, input.Password); err != nil {
		a.logger.Error("Failed to store password in RAM", zap.Error(err))
		// Don't fail login if password storage fails
	} else {
		a.logger.Info("Password stored securely in RAM for E2EE operations", zap.String("email", utils.MaskEmail(input.Email)))
	}

	// Cache master key for session to avoid re-decrypting for every file operation
	if input.Salt != "" && input.EncryptedMasterKey != "" && input.Password != "" {
		kdfAlgorithm := input.KDFAlgorithm
		if kdfAlgorithm == "" {
			kdfAlgorithm = e2ee.PBKDF2Algorithm
		}
		if err := a.cacheMasterKeyFromPassword(input.Email, input.Password, input.Salt, input.EncryptedMasterKey, kdfAlgorithm); err != nil {
			a.logger.Warn("Failed to cache master key during login", zap.Error(err))
			// Continue anyway - user can still use the app, just slower
		}
	}

	a.logger.Info("User logged in successfully", zap.String("email", utils.MaskEmail(input.Email)))

	// Initialize user-specific storage for the logged-in user
	if err := a.storageManager.InitializeForUser(input.Email); err != nil {
		a.logger.Error("Failed to initialize user storage", zap.Error(err))
		// Don't fail login - user can still use cloud features, just not local storage
	} else {
		a.logger.Info("User storage initialized", zap.String("email", utils.MaskEmail(input.Email)))
	}

	// Initialize search index for the logged-in user
	if err := a.searchService.Initialize(a.ctx, input.Email); err != nil {
		a.logger.Error("Failed to initialize search index", zap.Error(err))
		// Don't fail login if search initialization fails - it's not critical
		// The app can still function without search
	} else {
		a.logger.Info("Search index initialized", zap.String("email", utils.MaskEmail(input.Email)))

		// Rebuild search index from local data in the background
		userEmail := input.Email // Capture email before goroutine
		go func() {
			if err := a.rebuildSearchIndexForUser(userEmail); err != nil {
				a.logger.Warn("Failed to rebuild search index after login", zap.Error(err))
			}
		}()
	}

	// Start token manager for automatic token refresh
	a.tokenManager.Start()
	a.logger.Info("Token manager started for new session")

	return nil
}

// DecryptLoginChallenge decrypts the login challenge using the user's password.
// The kdfAlgorithm parameter specifies which key derivation function to use.
// If kdfAlgorithm is empty, it defaults to "PBKDF2-SHA256".
func (a *Application) DecryptLoginChallenge(password, saltBase64, encryptedMasterKeyBase64, encryptedChallengeBase64, encryptedPrivateKeyBase64, publicKeyBase64, kdfAlgorithm string) (string, error) {
	// Default to PBKDF2-SHA256
	if kdfAlgorithm == "" {
		kdfAlgorithm = e2ee.PBKDF2Algorithm
	}

	a.logger.Debug("Decrypting login challenge", zap.String("kdf_algorithm", kdfAlgorithm))

	// Decode base64 inputs
	salt, err := base64.StdEncoding.DecodeString(saltBase64)
	if err != nil {
		a.logger.Error("Failed to decode salt", zap.Error(err))
		return "", fmt.Errorf("invalid salt encoding: %w", err)
	}

	encryptedChallenge, err := base64.StdEncoding.DecodeString(encryptedChallengeBase64)
	if err != nil {
		a.logger.Error("Failed to decode encrypted challenge", zap.Error(err))
		return "", fmt.Errorf("invalid challenge encoding: %w", err)
	}

	publicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64)
	if err != nil {
		a.logger.Error("Failed to decode public key", zap.Error(err))
		return "", fmt.Errorf("invalid public key encoding: %w", err)
	}

	// Decode encrypted private key
	encryptedPrivateKeyCombined, err := base64.StdEncoding.DecodeString(encryptedPrivateKeyBase64)
	if err != nil {
		a.logger.Error("Failed to decode encrypted private key", zap.Error(err))
		return "", fmt.Errorf("invalid encrypted private key encoding: %w", err)
	}

	// Decode encrypted master key
	encryptedMasterKeyCombined, err := base64.StdEncoding.DecodeString(encryptedMasterKeyBase64)
	if err != nil {
		a.logger.Error("Failed to decode encrypted master key", zap.Error(err))
		return "", fmt.Errorf("invalid encrypted master key encoding: %w", err)
	}

	// 1. Derive KEK from password and salt using PBKDF2-SHA256
	keychain, err := e2ee.NewSecureKeyChainWithAlgorithm(password, salt, kdfAlgorithm)
	if err != nil {
		a.logger.Error("Failed to create secure keychain", zap.Error(err), zap.String("kdf_algorithm", kdfAlgorithm))
		return "", fmt.Errorf("failed to derive key from password: %w", err)
	}
	defer keychain.Clear()

	// 2. Decrypt master key with KEK into protected memory
	// Auto-detect nonce size: web frontend uses 24-byte nonces (XSalsa20), native uses 12-byte (ChaCha20)
	masterKeyNonce, masterKeyCiphertext, err := e2ee.SplitNonceAndCiphertextSecretBox(encryptedMasterKeyCombined)
	if err != nil {
		a.logger.Error("Failed to split encrypted master key", zap.Error(err))
		return "", fmt.Errorf("invalid encrypted master key format: %w", err)
	}

	encryptedMasterKeyStruct := &e2ee.EncryptedKey{
		Ciphertext: masterKeyCiphertext,
		Nonce:      masterKeyNonce,
	}

	masterKey, err := keychain.DecryptMasterKeySecure(encryptedMasterKeyStruct)
	if err != nil {
		a.logger.Error("Failed to decrypt master key", zap.Error(err), zap.String("kdf_algorithm", kdfAlgorithm))
		return "", fmt.Errorf("failed to decrypt master key (wrong password?): %w", err)
	}
	defer masterKey.Destroy()

	// 3. Decrypt private key with master key into protected memory
	// Auto-detect nonce size based on the encrypted data
	privateKeyNonce, privateKeyCiphertext, err := e2ee.SplitNonceAndCiphertextSecretBox(encryptedPrivateKeyCombined)
	if err != nil {
		a.logger.Error("Failed to split encrypted private key", zap.Error(err))
		return "", fmt.Errorf("invalid encrypted private key format: %w", err)
	}

	encryptedPrivateKeyStruct := &e2ee.EncryptedKey{
		Ciphertext: privateKeyCiphertext,
		Nonce:      privateKeyNonce,
	}

	privateKey, err := e2ee.DecryptPrivateKeySecure(encryptedPrivateKeyStruct, masterKey)
	if err != nil {
		a.logger.Error("Failed to decrypt private key", zap.Error(err))
		return "", fmt.Errorf("failed to decrypt private key: %w", err)
	}
	defer privateKey.Destroy()

	// 4. Decrypt the challenge using the private key (NaCl anonymous box)
	decryptedChallenge, err := e2ee.DecryptAnonymousBox(encryptedChallenge, publicKey, privateKey.Bytes())
	if err != nil {
		a.logger.Error("Failed to decrypt challenge", zap.Error(err))
		return "", fmt.Errorf("failed to decrypt login challenge: %w", err)
	}

	// Convert decrypted challenge to base64 for sending to server
	decryptedChallengeBase64 := base64.StdEncoding.EncodeToString(decryptedChallenge)

	a.logger.Info("Successfully decrypted login challenge")
	return decryptedChallengeBase64, nil
}

// RegistrationKeys contains all the E2EE keys needed for registration
type RegistrationKeys struct {
	Salt                              string `json:"salt"`
	EncryptedMasterKey                string `json:"encryptedMasterKey"`
	PublicKey                         string `json:"publicKey"`
	EncryptedPrivateKey               string `json:"encryptedPrivateKey"`
	EncryptedRecoveryKey              string `json:"encryptedRecoveryKey"`
	MasterKeyEncryptedWithRecoveryKey string `json:"masterKeyEncryptedWithRecoveryKey"`
	// RecoveryMnemonic is the 12-word BIP39 mnemonic phrase that must be shown to the user
	// The user MUST save this phrase securely - it's their only way to recover their account
	RecoveryMnemonic string `json:"recoveryMnemonic"`
}

// RecoveryInitiateResponse contains the response from initiating account recovery
type RecoveryInitiateResponse struct {
	Message            string `json:"message"`
	SessionID          string `json:"sessionId"`
	EncryptedChallenge string `json:"encryptedChallenge"`
}

// InitiateRecovery starts the account recovery process for the given email
func (a *Application) InitiateRecovery(email string) (*RecoveryInitiateResponse, error) {
	// Validate input
	if err := inputvalidation.ValidateEmail(email); err != nil {
		return nil, err
	}

	// Check rate limit before making request
	if err := a.rateLimiter.Check(ratelimiter.OpRequestOTT, email); err != nil {
		a.logger.Warn("Recovery initiation rate limited",
			zap.String("email", utils.MaskEmail(email)),
			zap.Error(err))
		return nil, err
	}

	resp, err := a.authService.InitiateRecovery(a.ctx, email, "recovery_key")
	if err != nil {
		a.logger.Error("Recovery initiation failed", zap.Error(err))
		return nil, err
	}

	a.logger.Info("Recovery initiated successfully", zap.String("email", utils.MaskEmail(email)))

	return &RecoveryInitiateResponse{
		Message:            resp.Message,
		SessionID:          resp.SessionID,
		EncryptedChallenge: resp.EncryptedChallenge,
	}, nil
}

// DecryptRecoveryChallengeInput contains the data needed to process recovery challenge
type DecryptRecoveryChallengeInput struct {
	RecoveryMnemonic   string `json:"recoveryMnemonic"`
	EncryptedChallenge string `json:"encryptedChallenge"`
}

// DecryptRecoveryChallengeResult contains the result of processing recovery challenge
type DecryptRecoveryChallengeResult struct {
	DecryptedChallenge string `json:"decryptedChallenge"`
	IsValid            bool   `json:"isValid"`
}

// DecryptRecoveryChallenge validates the recovery mnemonic and processes the challenge.
// Note: The backend currently sends an unencrypted challenge (base64-encoded plaintext).
// This function validates the recovery phrase format and passes through the challenge.
// When the backend implements proper encryption, this function will decrypt the challenge.
func (a *Application) DecryptRecoveryChallenge(input *DecryptRecoveryChallengeInput) (*DecryptRecoveryChallengeResult, error) {
	// Validate recovery mnemonic (must be 12 words)
	if input.RecoveryMnemonic == "" {
		return nil, fmt.Errorf("recovery mnemonic is required")
	}

	// Validate the mnemonic is a valid BIP39 phrase
	if !bip39.IsMnemonicValid(input.RecoveryMnemonic) {
		a.logger.Warn("Invalid recovery mnemonic format")
		return nil, fmt.Errorf("invalid recovery phrase: must be 12 valid BIP39 words")
	}

	// Count words to ensure we have exactly 12
	words := len(splitMnemonic(input.RecoveryMnemonic))
	if words != 12 {
		return nil, fmt.Errorf("invalid recovery phrase: must be exactly 12 words, got %d", words)
	}

	// Validate the encrypted challenge is present
	if input.EncryptedChallenge == "" {
		return nil, fmt.Errorf("encrypted challenge is required")
	}

	// Derive recovery key from mnemonic to validate it's a valid recovery phrase
	// This also prepares for future decryption when backend implements encryption
	seed := bip39.NewSeed(input.RecoveryMnemonic, "")
	recoveryKey := seed[:32]

	a.logger.Debug("Recovery key derived successfully",
		zap.Int("key_length", len(recoveryKey)),
		zap.Int("word_count", words))

	// TEMPORARY WORKAROUND: Backend currently sends base64-encoded plaintext challenge
	// instead of encrypted challenge. See backend TODO in recovery_initiate.go:108-113
	// Until backend implements proper encryption, we just validate and pass through.

	// Decode the challenge to validate it's valid base64
	challengeBytes, err := base64.StdEncoding.DecodeString(input.EncryptedChallenge)
	if err != nil {
		a.logger.Error("Failed to decode challenge", zap.Error(err))
		return nil, fmt.Errorf("invalid challenge format: %w", err)
	}

	// Re-encode to base64 for sending to backend
	decryptedChallengeBase64 := base64.StdEncoding.EncodeToString(challengeBytes)

	a.logger.Info("Recovery challenge processed successfully (backend workaround active)")

	return &DecryptRecoveryChallengeResult{
		DecryptedChallenge: decryptedChallengeBase64,
		IsValid:            true,
	}, nil
}

// splitMnemonic splits a mnemonic phrase into words
func splitMnemonic(mnemonic string) []string {
	var words []string
	for _, word := range splitByWhitespace(mnemonic) {
		if word != "" {
			words = append(words, word)
		}
	}
	return words
}

// splitByWhitespace splits a string by whitespace characters
func splitByWhitespace(s string) []string {
	return splitString(s)
}

// splitString splits a string into words by spaces
func splitString(s string) []string {
	var result []string
	word := ""
	for _, r := range s {
		if r == ' ' || r == '\t' || r == '\n' || r == '\r' {
			if word != "" {
				result = append(result, word)
				word = ""
			}
		} else {
			word += string(r)
		}
	}
	if word != "" {
		result = append(result, word)
	}
	return result
}

// RecoveryVerifyResponse contains the response from verifying recovery
type RecoveryVerifyResponse struct {
	Message             string `json:"message"`
	RecoveryToken       string `json:"recoveryToken"`
	CanResetCredentials bool   `json:"canResetCredentials"`
}

// VerifyRecovery verifies the recovery challenge with the server
func (a *Application) VerifyRecovery(sessionID, decryptedChallenge string) (*RecoveryVerifyResponse, error) {
	if sessionID == "" {
		return nil, fmt.Errorf("session ID is required")
	}
	if decryptedChallenge == "" {
		return nil, fmt.Errorf("decrypted challenge is required")
	}

	input := &client.RecoveryVerifyInput{
		SessionID:          sessionID,
		DecryptedChallenge: decryptedChallenge,
	}

	resp, err := a.authService.VerifyRecovery(a.ctx, input)
	if err != nil {
		a.logger.Error("Recovery verification failed", zap.Error(err))
		return nil, err
	}

	a.logger.Info("Recovery verification successful")

	return &RecoveryVerifyResponse{
		Message:             resp.Message,
		RecoveryToken:       resp.RecoveryToken,
		CanResetCredentials: resp.CanResetCredentials,
	}, nil
}

// CompleteRecoveryInput contains the data needed to complete account recovery
type CompleteRecoveryInput struct {
	RecoveryToken    string `json:"recoveryToken"`
	RecoveryMnemonic string `json:"recoveryMnemonic"`
	NewPassword      string `json:"newPassword"`
}

// CompleteRecoveryResponse contains the response from completing recovery
type CompleteRecoveryResponse struct {
	Message string `json:"message"`
	Success bool   `json:"success"`
}

// CompleteRecovery completes the account recovery by re-encrypting keys with a new password.
// This function:
// 1. Validates the recovery mnemonic
// 2. Derives the recovery key from the mnemonic
// 3. Generates new encryption keys with the new password
// 4. Sends the new encrypted keys to the server
func (a *Application) CompleteRecovery(input *CompleteRecoveryInput) (*CompleteRecoveryResponse, error) {
	// Validate inputs
	if input.RecoveryToken == "" {
		return nil, fmt.Errorf("recovery token is required")
	}
	if input.RecoveryMnemonic == "" {
		return nil, fmt.Errorf("recovery mnemonic is required")
	}
	if err := inputvalidation.ValidatePassword(input.NewPassword); err != nil {
		return nil, err
	}

	// Validate the mnemonic is a valid BIP39 phrase
	if !bip39.IsMnemonicValid(input.RecoveryMnemonic) {
		return nil, fmt.Errorf("invalid recovery phrase: must be 12 valid BIP39 words")
	}

	// Count words to ensure we have exactly 12
	words := len(splitMnemonic(input.RecoveryMnemonic))
	if words != 12 {
		return nil, fmt.Errorf("invalid recovery phrase: must be exactly 12 words, got %d", words)
	}

	a.logger.Info("Starting recovery completion - generating new encryption keys")

	// 1. Derive recovery key from mnemonic
	seed := bip39.NewSeed(input.RecoveryMnemonic, "")
	recoveryKeyBytes := seed[:32]
	recoveryKey, err := e2ee.NewSecureBuffer(recoveryKeyBytes)
	if err != nil {
		e2ee.ClearBytes(recoveryKeyBytes)
		return nil, fmt.Errorf("failed to create secure buffer for recovery key: %w", err)
	}
	defer recoveryKey.Destroy()
	e2ee.ClearBytes(recoveryKeyBytes)

	// 2. Generate new salt for the new password
	newSalt, err := e2ee.GenerateSalt()
	if err != nil {
		return nil, fmt.Errorf("failed to generate new salt: %w", err)
	}

	// 3. Create new keychain with PBKDF2-SHA256 (for web frontend compatibility)
	newKeychain, err := e2ee.NewSecureKeyChainWithAlgorithm(input.NewPassword, newSalt, e2ee.PBKDF2Algorithm)
	if err != nil {
		return nil, fmt.Errorf("failed to create new keychain: %w", err)
	}
	defer newKeychain.Clear()

	// 4. Generate new master key
	masterKeyBytes, err := e2ee.GenerateMasterKey()
	if err != nil {
		return nil, fmt.Errorf("failed to generate new master key: %w", err)
	}
	masterKey, err := e2ee.NewSecureBuffer(masterKeyBytes)
	if err != nil {
		e2ee.ClearBytes(masterKeyBytes)
		return nil, fmt.Errorf("failed to create secure buffer for master key: %w", err)
	}
	defer masterKey.Destroy()
	e2ee.ClearBytes(masterKeyBytes)

	// 5. Encrypt master key with new KEK
	encryptedMasterKey, err := newKeychain.EncryptMasterKeySecretBox(masterKey.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key: %w", err)
	}

	// 6. Generate new keypair
	newPublicKey, privateKeyBytes, err := e2ee.GenerateKeyPair()
	if err != nil {
		return nil, fmt.Errorf("failed to generate new keypair: %w", err)
	}
	privateKey, err := e2ee.NewSecureBuffer(privateKeyBytes)
	if err != nil {
		e2ee.ClearBytes(privateKeyBytes)
		return nil, fmt.Errorf("failed to create secure buffer for private key: %w", err)
	}
	defer privateKey.Destroy()
	e2ee.ClearBytes(privateKeyBytes)

	// 7. Encrypt private key with master key
	encryptedPrivateKey, err := e2ee.EncryptPrivateKeySecretBox(privateKey.Bytes(), masterKey.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt private key: %w", err)
	}

	// 8. Encrypt recovery key with master key
	encryptedRecoveryKey, err := e2ee.EncryptRecoveryKeySecretBox(recoveryKey.Bytes(), masterKey.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt recovery key: %w", err)
	}

	// 9. Encrypt master key with recovery key (for future recovery)
	masterKeyEncryptedWithRecoveryKey, err := e2ee.EncryptMasterKeyWithRecoveryKeySecretBox(masterKey.Bytes(), recoveryKey.Bytes())
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt master key with recovery key: %w", err)
	}

	// 10. Convert all keys to base64 for transport
	newSaltBase64 := base64.StdEncoding.EncodeToString(newSalt)
	newPublicKeyBase64 := base64.StdEncoding.EncodeToString(newPublicKey)
	newEncryptedMasterKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedMasterKey.Nonce, encryptedMasterKey.Ciphertext),
	)
	newEncryptedPrivateKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedPrivateKey.Nonce, encryptedPrivateKey.Ciphertext),
	)
	newEncryptedRecoveryKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedRecoveryKey.Nonce, encryptedRecoveryKey.Ciphertext),
	)
	newMasterKeyEncryptedWithRecoveryKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(masterKeyEncryptedWithRecoveryKey.Nonce, masterKeyEncryptedWithRecoveryKey.Ciphertext),
	)

	// 11. Call API to complete recovery
	apiInput := &client.RecoveryCompleteInput{
		RecoveryToken:                        input.RecoveryToken,
		NewSalt:                              newSaltBase64,
		NewPublicKey:                         newPublicKeyBase64,
		NewEncryptedMasterKey:                newEncryptedMasterKeyBase64,
		NewEncryptedPrivateKey:               newEncryptedPrivateKeyBase64,
		NewEncryptedRecoveryKey:              newEncryptedRecoveryKeyBase64,
		NewMasterKeyEncryptedWithRecoveryKey: newMasterKeyEncryptedWithRecoveryKeyBase64,
	}

	resp, err := a.authService.CompleteRecovery(a.ctx, apiInput)
	if err != nil {
		a.logger.Error("Recovery completion failed", zap.Error(err))
		return nil, err
	}

	a.logger.Info("Recovery completed successfully - new encryption keys set")

	return &CompleteRecoveryResponse{
		Message: resp.Message,
		Success: resp.Success,
	}, nil
}

// GenerateRegistrationKeys generates all E2EE keys needed for user registration.
// This function uses PBKDF2-SHA256 for key derivation and XSalsa20-Poly1305 (SecretBox)
// for symmetric encryption to ensure compatibility with the web frontend.
func (a *Application) GenerateRegistrationKeys(password string) (*RegistrationKeys, error) {
	// 1. Generate salt (16 bytes for PBKDF2)
	salt, err := e2ee.GenerateSalt()
	if err != nil {
		a.logger.Error("Failed to generate salt", zap.Error(err))
		return nil, err
	}

	// 2. Create secure keychain using PBKDF2-SHA256 (compatible with web frontend)
	// This derives KEK from password + salt using PBKDF2-SHA256 with 100,000 iterations
	keychain, err := e2ee.NewSecureKeyChainWithAlgorithm(password, salt, e2ee.PBKDF2Algorithm)
	if err != nil {
		a.logger.Error("Failed to create secure keychain", zap.Error(err))
		return nil, err
	}
	defer keychain.Clear() // Clear sensitive data when done

	// 3. Generate master key in protected memory
	masterKeyBytes, err := e2ee.GenerateMasterKey()
	if err != nil {
		a.logger.Error("Failed to generate master key", zap.Error(err))
		return nil, err
	}
	masterKey, err := e2ee.NewSecureBuffer(masterKeyBytes)
	if err != nil {
		e2ee.ClearBytes(masterKeyBytes)
		a.logger.Error("Failed to create secure buffer for master key", zap.Error(err))
		return nil, err
	}
	defer masterKey.Destroy()
	e2ee.ClearBytes(masterKeyBytes)

	// 4. Encrypt master key with KEK using XSalsa20-Poly1305 (SecretBox)
	// This produces 24-byte nonces compatible with web frontend's libsodium
	encryptedMasterKey, err := keychain.EncryptMasterKeySecretBox(masterKey.Bytes())
	if err != nil {
		a.logger.Error("Failed to encrypt master key", zap.Error(err))
		return nil, err
	}

	// 5. Generate NaCl keypair for asymmetric encryption
	publicKey, privateKeyBytes, err := e2ee.GenerateKeyPair()
	if err != nil {
		a.logger.Error("Failed to generate keypair", zap.Error(err))
		return nil, err
	}
	privateKey, err := e2ee.NewSecureBuffer(privateKeyBytes)
	if err != nil {
		e2ee.ClearBytes(privateKeyBytes)
		a.logger.Error("Failed to create secure buffer for private key", zap.Error(err))
		return nil, err
	}
	defer privateKey.Destroy()
	e2ee.ClearBytes(privateKeyBytes)

	// 6. Encrypt private key with master key using XSalsa20-Poly1305 (SecretBox)
	encryptedPrivateKey, err := e2ee.EncryptPrivateKeySecretBox(privateKey.Bytes(), masterKey.Bytes())
	if err != nil {
		a.logger.Error("Failed to encrypt private key", zap.Error(err))
		return nil, err
	}

	// 7. Generate BIP39 mnemonic (12 words) for account recovery
	// This matches the web frontend's approach for cross-platform compatibility
	entropy := make([]byte, 16) // 128 bits = 12 words
	if _, err := rand.Read(entropy); err != nil {
		a.logger.Error("Failed to generate entropy for recovery mnemonic", zap.Error(err))
		return nil, err
	}

	recoveryMnemonic, err := bip39.NewMnemonic(entropy)
	if err != nil {
		a.logger.Error("Failed to generate recovery mnemonic", zap.Error(err))
		return nil, err
	}
	a.logger.Info("Generated 12-word recovery mnemonic")

	// Convert mnemonic to seed (64 bytes via HMAC-SHA512) then take first 32 bytes
	// This matches web frontend's mnemonicToRecoveryKey() function
	seed := bip39.NewSeed(recoveryMnemonic, "") // Empty passphrase like web frontend
	recoveryKeyBytes := seed[:32]               // Use first 32 bytes as recovery key

	recoveryKey, err := e2ee.NewSecureBuffer(recoveryKeyBytes)
	if err != nil {
		e2ee.ClearBytes(recoveryKeyBytes)
		a.logger.Error("Failed to create secure buffer for recovery key", zap.Error(err))
		return nil, err
	}
	defer recoveryKey.Destroy()
	e2ee.ClearBytes(recoveryKeyBytes)

	// 8. Encrypt recovery key with master key using XSalsa20-Poly1305 (SecretBox)
	encryptedRecoveryKey, err := e2ee.EncryptRecoveryKeySecretBox(recoveryKey.Bytes(), masterKey.Bytes())
	if err != nil {
		a.logger.Error("Failed to encrypt recovery key", zap.Error(err))
		return nil, err
	}

	// 9. Encrypt master key with recovery key using XSalsa20-Poly1305 (SecretBox)
	masterKeyEncryptedWithRecoveryKey, err := e2ee.EncryptMasterKeyWithRecoveryKeySecretBox(masterKey.Bytes(), recoveryKey.Bytes())
	if err != nil {
		a.logger.Error("Failed to encrypt master key with recovery key", zap.Error(err))
		return nil, err
	}

	// Convert all keys to base64 for transport
	// Combine nonce and ciphertext for each encrypted key
	encryptedMasterKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedMasterKey.Nonce, encryptedMasterKey.Ciphertext),
	)
	encryptedPrivateKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedPrivateKey.Nonce, encryptedPrivateKey.Ciphertext),
	)
	encryptedRecoveryKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(encryptedRecoveryKey.Nonce, encryptedRecoveryKey.Ciphertext),
	)
	masterKeyEncryptedWithRecoveryKeyBase64 := base64.StdEncoding.EncodeToString(
		e2ee.CombineNonceAndCiphertext(masterKeyEncryptedWithRecoveryKey.Nonce, masterKeyEncryptedWithRecoveryKey.Ciphertext),
	)

	a.logger.Info("Successfully generated E2EE registration keys using PBKDF2-SHA256 + XSalsa20-Poly1305")

	return &RegistrationKeys{
		Salt:                              base64.StdEncoding.EncodeToString(salt),
		EncryptedMasterKey:                encryptedMasterKeyBase64,
		PublicKey:                         base64.StdEncoding.EncodeToString(publicKey),
		EncryptedPrivateKey:               encryptedPrivateKeyBase64,
		EncryptedRecoveryKey:              encryptedRecoveryKeyBase64,
		MasterKeyEncryptedWithRecoveryKey: masterKeyEncryptedWithRecoveryKeyBase64,
		RecoveryMnemonic:                  recoveryMnemonic,
	}, nil
}
