package passwordstore

import (
	"fmt"
	"sync"

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

	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
)

// Service manages password storage in secure RAM
type Service struct {
	logger *zap.Logger
	mu     sync.RWMutex

	// RAM storage (memguard enclaves) - email -> encrypted password
	memoryStore map[string]*memguard.Enclave
}

// New creates a new password storage service
func New(logger *zap.Logger) *Service {
	// Initialize memguard
	memguard.CatchInterrupt()

	return &Service{
		logger:      logger,
		memoryStore: make(map[string]*memguard.Enclave),
	}
}

// StorePassword stores password in secure RAM (memguard).
// SECURITY NOTE: This method accepts a string for API compatibility with JSON inputs.
// The string is immediately converted to []byte and the byte slice is zeroed after
// creating the secure enclave. For maximum security, use StorePasswordBytes when
// you have direct access to []byte data.
func (s *Service) StorePassword(email, password string) error {
	// Convert string to byte slice for secure handling
	passwordBytes := []byte(password)

	// Store using the secure byte-based method
	err := s.StorePasswordBytes(email, passwordBytes)

	// Zero the byte slice after use (defense in depth)
	// Note: The original string cannot be zeroed in Go, but we minimize exposure
	// by zeroing the byte slice copy as soon as possible
	zeroBytes(passwordBytes)

	return err
}

// StorePasswordBytes stores password from []byte in secure RAM (memguard).
// This is the preferred method when you have direct access to password bytes,
// as it allows the caller to zero the source bytes after this call returns.
// The provided byte slice is copied into secure memory and can be safely zeroed
// by the caller after this method returns.
func (s *Service) StorePasswordBytes(email string, password []byte) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Remove existing enclave if any (will be garbage collected)
	if _, exists := s.memoryStore[email]; exists {
		delete(s.memoryStore, email)
		s.logger.Debug("Replaced existing password enclave",
			zap.String("email", utils.MaskEmail(email)))
	}

	// Create new secure enclave (memguard copies the data into protected memory)
	enclave := memguard.NewEnclave(password)
	s.memoryStore[email] = enclave

	s.logger.Debug("Password stored in secure RAM",
		zap.String("email", utils.MaskEmail(email)))

	return nil
}

// GetPassword retrieves password from secure RAM as a string.
// SECURITY NOTE: The returned string cannot be zeroed in Go. For operations
// that can work with []byte, use GetPasswordBytes instead.
func (s *Service) GetPassword(email string) (string, error) {
	passwordBytes, err := s.GetPasswordBytes(email)
	if err != nil {
		return "", err
	}

	// Convert to string (unfortunately creates a copy that can't be zeroed)
	password := string(passwordBytes)

	// Zero the byte slice
	zeroBytes(passwordBytes)

	return password, nil
}

// GetPasswordBytes retrieves password from secure RAM as []byte.
// The caller SHOULD zero the returned byte slice after use by calling
// zeroBytes or similar. This is the preferred method for security-sensitive
// operations.
func (s *Service) GetPasswordBytes(email string) ([]byte, error) {
	s.mu.RLock()
	enclave, exists := s.memoryStore[email]
	s.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("no password stored for %s", email)
	}

	// Open enclave to read password
	lockedBuffer, err := enclave.Open()
	if err != nil {
		return nil, fmt.Errorf("failed to open password enclave: %w", err)
	}
	defer lockedBuffer.Destroy()

	// Copy the password bytes (memguard buffer will be destroyed after defer)
	passwordBytes := make([]byte, len(lockedBuffer.Bytes()))
	copy(passwordBytes, lockedBuffer.Bytes())

	return passwordBytes, nil
}

// HasPassword checks if password is stored for given email
func (s *Service) HasPassword(email string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()

	_, exists := s.memoryStore[email]
	return exists
}

// ClearPassword removes password from RAM (logout)
func (s *Service) ClearPassword(email string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, exists := s.memoryStore[email]; exists {
		delete(s.memoryStore, email)
		s.logger.Debug("Password cleared from RAM",
			zap.String("email", utils.MaskEmail(email)))
	}

	return nil
}

// Cleanup destroys all secure memory (called on shutdown)
func (s *Service) Cleanup() {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Log cleanup for each stored password
	for email := range s.memoryStore {
		s.logger.Debug("Clearing password enclave on shutdown",
			zap.String("email", utils.MaskEmail(email)))
	}

	// Clear the map (enclaves will be garbage collected)
	s.memoryStore = make(map[string]*memguard.Enclave)

	// Purge all memguard secure memory
	memguard.Purge()

	s.logger.Debug("Password store cleanup complete - all secure memory purged")
}

// zeroBytes overwrites a byte slice with zeros to clear sensitive data from memory.
// This is a defense-in-depth measure - while Go's GC may still have copies,
// this reduces the window of exposure.
func zeroBytes(b []byte) {
	for i := range b {
		b[i] = 0
	}
}

// ZeroBytes is exported for callers who receive password bytes and need to clear them.
// Use this after you're done with password bytes returned by GetPasswordBytes.
func ZeroBytes(b []byte) {
	zeroBytes(b)
}
