// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/auth/recovery_complete.go
package auth

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

	"github.com/awnumar/memguard"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/crypto"
	dom_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/user"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/auditlog"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/storage/cache/cassandracache"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/transaction"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

type RecoveryCompleteRequestDTO struct {
	RecoveryToken                        string `json:"recovery_token"`
	NewSalt                              string `json:"new_salt"`
	NewPublicKey                         string `json:"new_public_key"`
	NewEncryptedMasterKey                string `json:"new_encrypted_master_key"`
	NewEncryptedPrivateKey               string `json:"new_encrypted_private_key"`
	NewEncryptedRecoveryKey              string `json:"new_encrypted_recovery_key"`
	NewMasterKeyEncryptedWithRecoveryKey string `json:"new_master_key_encrypted_with_recovery_key"`
}

type RecoveryCompleteResponseDTO struct {
	Message string `json:"message"`
	Success bool   `json:"success"`
}

type RecoveryCompleteService interface {
	Execute(ctx context.Context, req *RecoveryCompleteRequestDTO) (*RecoveryCompleteResponseDTO, error)
}

type recoveryCompleteServiceImpl struct {
	logger           *zap.Logger
	auditLogger      auditlog.AuditLogger
	userGetByEmailUC uc_user.UserGetByEmailUseCase
	userUpdateUC     uc_user.UserUpdateUseCase
	cache            cassandracache.CassandraCacher
}

func NewRecoveryCompleteService(
	logger *zap.Logger,
	auditLogger auditlog.AuditLogger,
	userGetByEmailUC uc_user.UserGetByEmailUseCase,
	userUpdateUC uc_user.UserUpdateUseCase,
	cache cassandracache.CassandraCacher,
) RecoveryCompleteService {
	return &recoveryCompleteServiceImpl{
		logger:           logger.Named("RecoveryCompleteService"),
		auditLogger:      auditLogger,
		userGetByEmailUC: userGetByEmailUC,
		userUpdateUC:     userUpdateUC,
		cache:            cache,
	}
}

func (s *recoveryCompleteServiceImpl) Execute(ctx context.Context, req *RecoveryCompleteRequestDTO) (*RecoveryCompleteResponseDTO, error) {
	// Create SAGA for recovery completion workflow
	saga := transaction.NewSaga("recovery-complete", s.logger)

	s.logger.Info("starting recovery completion")

	// Step 1: Validate recovery token from cache
	tokenKey := fmt.Sprintf("recovery_token:%s", req.RecoveryToken)
	emailBytes, err := s.cache.Get(ctx, tokenKey)
	if err != nil || emailBytes == nil {
		s.logger.Warn("Recovery token not found or expired")
		return nil, fmt.Errorf("invalid or expired recovery token")
	}

	email := string(emailBytes)

	// Step 2: Get user by email and backup current credentials
	user, err := s.userGetByEmailUC.Execute(ctx, email)
	if err != nil || user == nil {
		s.logger.Error("User not found during recovery completion", zap.String("email", validation.MaskEmail(email)))
		return nil, fmt.Errorf("recovery completion failed")
	}

	// Backup current credentials for compensation (deep copy)
	var oldSecurityData *dom_user.UserSecurityData
	if user.SecurityData != nil {
		// Create a deep copy of security data
		oldSecurityData = &dom_user.UserSecurityData{
			PasswordSalt:                      make([]byte, len(user.SecurityData.PasswordSalt)),
			PublicKey:                         user.SecurityData.PublicKey,
			EncryptedMasterKey:                user.SecurityData.EncryptedMasterKey,
			EncryptedPrivateKey:               user.SecurityData.EncryptedPrivateKey,
			EncryptedRecoveryKey:              user.SecurityData.EncryptedRecoveryKey,
			MasterKeyEncryptedWithRecoveryKey: user.SecurityData.MasterKeyEncryptedWithRecoveryKey,
		}
		copy(oldSecurityData.PasswordSalt, user.SecurityData.PasswordSalt)
	}

	// Decode new encryption keys from base64
	// SECURITY: All decoded key material is wiped from memory after use
	newSalt, err := base64.StdEncoding.DecodeString(req.NewSalt)
	if err != nil {
		return nil, fmt.Errorf("invalid salt format")
	}
	defer memguard.WipeBytes(newSalt)

	newPublicKey, err := base64.StdEncoding.DecodeString(req.NewPublicKey)
	if err != nil {
		return nil, fmt.Errorf("invalid public key format")
	}
	defer memguard.WipeBytes(newPublicKey)

	newEncryptedMasterKey, err := base64.StdEncoding.DecodeString(req.NewEncryptedMasterKey)
	if err != nil {
		return nil, fmt.Errorf("invalid encrypted master key format")
	}
	defer memguard.WipeBytes(newEncryptedMasterKey)

	newEncryptedPrivateKey, err := base64.StdEncoding.DecodeString(req.NewEncryptedPrivateKey)
	if err != nil {
		return nil, fmt.Errorf("invalid encrypted private key format")
	}
	defer memguard.WipeBytes(newEncryptedPrivateKey)

	newEncryptedRecoveryKey, err := base64.StdEncoding.DecodeString(req.NewEncryptedRecoveryKey)
	if err != nil {
		return nil, fmt.Errorf("invalid encrypted recovery key format")
	}
	defer memguard.WipeBytes(newEncryptedRecoveryKey)

	newMasterKeyEncryptedWithRecovery, err := base64.StdEncoding.DecodeString(req.NewMasterKeyEncryptedWithRecoveryKey)
	if err != nil {
		return nil, fmt.Errorf("invalid master key encrypted with recovery format")
	}
	defer memguard.WipeBytes(newMasterKeyEncryptedWithRecovery)

	// Update user's encryption keys
	if user.SecurityData == nil {
		user.SecurityData = &dom_user.UserSecurityData{}
	}

	// Parse the encrypted keys into their proper structures
	// Format: nonce (24 bytes) + ciphertext (remaining bytes)

	// Update password salt
	user.SecurityData.PasswordSalt = newSalt

	// Update public key (critical for login challenge encryption)
	user.SecurityData.PublicKey = crypto.PublicKey{
		Key: newPublicKey,
	}

	// Update encrypted master key
	if len(newEncryptedMasterKey) > 24 {
		user.SecurityData.EncryptedMasterKey = crypto.EncryptedMasterKey{
			Nonce:      newEncryptedMasterKey[:24],
			Ciphertext: newEncryptedMasterKey[24:],
			KeyVersion: 1,
		}
	}

	// Update encrypted private key
	if len(newEncryptedPrivateKey) > 24 {
		user.SecurityData.EncryptedPrivateKey = crypto.EncryptedPrivateKey{
			Nonce:      newEncryptedPrivateKey[:24],
			Ciphertext: newEncryptedPrivateKey[24:],
		}
	}

	// Update encrypted recovery key
	if len(newEncryptedRecoveryKey) > 24 {
		user.SecurityData.EncryptedRecoveryKey = crypto.EncryptedRecoveryKey{
			Nonce:      newEncryptedRecoveryKey[:24],
			Ciphertext: newEncryptedRecoveryKey[24:],
		}
	}

	// Update master key encrypted with recovery key
	if len(newMasterKeyEncryptedWithRecovery) > 24 {
		user.SecurityData.MasterKeyEncryptedWithRecoveryKey = crypto.MasterKeyEncryptedWithRecoveryKey{
			Nonce:      newMasterKeyEncryptedWithRecovery[:24],
			Ciphertext: newMasterKeyEncryptedWithRecovery[24:],
		}
	}

	// Update user's modified timestamp
	user.ModifiedAt = time.Now()

	// Step 3: Save updated user with new credentials (compensate: restore old credentials)
	// CRITICAL: This must succeed before token deletion to prevent account takeover
	if err := s.userUpdateUC.Execute(ctx, user); err != nil {
		s.logger.Error("Failed to update user with new keys", zap.Error(err))
		return nil, fmt.Errorf("failed to complete recovery")
	}

	// Register compensation: restore old credentials if token deletion fails
	userCaptured := user
	oldSecurityDataCaptured := oldSecurityData
	saga.AddCompensation(func(ctx context.Context) error {
		s.logger.Warn("compensating: restoring old credentials",
			zap.String("user_id", userCaptured.ID.String()))

		// Restore old security data
		userCaptured.SecurityData = oldSecurityDataCaptured
		userCaptured.ModifiedAt = time.Now()

		if err := s.userUpdateUC.Execute(ctx, userCaptured); err != nil {
			s.logger.Error("Failed to restore old credentials during compensation",
				zap.String("user_id", userCaptured.ID.String()),
				zap.Error(err))
			return fmt.Errorf("compensation failed: %w", err)
		}

		s.logger.Info("old credentials restored successfully during compensation",
			zap.String("user_id", userCaptured.ID.String()))
		return nil
	})

	// Step 4: Clear recovery token (one-time use) - MUST succeed to prevent reuse
	// CRITICAL: If this fails, recovery token could be reused for account takeover
	tokenKeyCaptured := tokenKey
	if err := s.cache.Delete(ctx, tokenKeyCaptured); err != nil {
		s.logger.Error("Failed to delete recovery token - SECURITY RISK",
			zap.String("token_key", tokenKeyCaptured),
			zap.Error(err))

		// Trigger compensation: Restore old credentials
		saga.Rollback(ctx)

		return nil, fmt.Errorf("failed to invalidate recovery token - please contact support")
	}

	s.logger.Info("Recovery completion successful",
		zap.String("email", validation.MaskEmail(email)),
		zap.String("user_id", user.ID.String()))

	// Audit log recovery completion
	s.auditLogger.LogAuth(ctx, auditlog.EventTypeRecoveryCompleted, auditlog.OutcomeSuccess,
		validation.MaskEmail(email), "", map[string]string{
			"user_id": user.ID.String(),
		})

	return &RecoveryCompleteResponseDTO{
		Message: "Account recovery completed successfully. You can now log in with your new credentials.",
		Success: true,
	}, nil
}
