// Package storagemanager provides a service for managing user-specific storage.
// It handles the lifecycle of storage instances, creating new storage when a user
// logs in and cleaning up when they log out.
//
// Storage is organized as follows:
// - Global storage (session): {appDir}/session/ - stores current login session
// - User-specific storage: {appDir}/users/{emailHash}/ - stores user data
//
// This ensures:
// 1. Different users have completely isolated data
// 2. Dev and production modes have separate directories
// 3. Email addresses are not exposed in directory names (hashed)
package storagemanager

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/config"
	collectionDomain "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/collection"
	fileDomain "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/file"
	syncstateDomain "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/domain/syncstate"
	collectionRepo "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/repo/collection"
	fileRepo "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/repo/file"
	syncstateRepo "codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/repo/syncstate"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage"
	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage/leveldb"
)

// Manager manages user-specific storage instances.
// It creates storage when a user logs in and cleans up when they log out.
type Manager struct {
	logger *zap.Logger
	mu     sync.RWMutex

	// Current user's email (empty if no user is logged in)
	currentUserEmail string

	// User-specific storage instances
	localFilesStorage storage.Storage
	syncStateStorage  storage.Storage

	// User-specific repositories (built on top of storage)
	fileRepo       fileDomain.Repository
	collectionRepo collectionDomain.Repository
	syncStateRepo  syncstateDomain.Repository
}

// ProvideManager creates a new storage manager.
func ProvideManager(logger *zap.Logger) *Manager {
	return &Manager{
		logger: logger.Named("storage-manager"),
	}
}

// InitializeForUser initializes user-specific storage for the given user.
// This should be called after a user successfully logs in.
// If storage is already initialized for a different user, it will be cleaned up first.
func (m *Manager) InitializeForUser(userEmail string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if userEmail == "" {
		return fmt.Errorf("user email is required")
	}

	// If same user, no need to reinitialize
	if m.currentUserEmail == userEmail && m.localFilesStorage != nil {
		m.logger.Debug("Storage already initialized for user",
			zap.String("email_hash", config.GetEmailHashForPath(userEmail)))
		return nil
	}

	// Clean up existing storage if different user
	if m.currentUserEmail != "" && m.currentUserEmail != userEmail {
		m.logger.Info("Switching user storage",
			zap.String("old_user_hash", config.GetEmailHashForPath(m.currentUserEmail)),
			zap.String("new_user_hash", config.GetEmailHashForPath(userEmail)))
		m.cleanupStorageUnsafe()
	}

	m.logger.Info("Initializing storage for user",
		zap.String("email_hash", config.GetEmailHashForPath(userEmail)))

	// Initialize local files storage
	localFilesProvider, err := config.NewLevelDBConfigurationProviderForLocalFilesWithUser(userEmail)
	if err != nil {
		return fmt.Errorf("failed to create local files storage provider: %w", err)
	}
	m.localFilesStorage = leveldb.NewDiskStorage(localFilesProvider, m.logger.Named("local-files"))

	// Initialize sync state storage
	syncStateProvider, err := config.NewLevelDBConfigurationProviderForSyncStateWithUser(userEmail)
	if err != nil {
		m.cleanupStorageUnsafe()
		return fmt.Errorf("failed to create sync state storage provider: %w", err)
	}
	m.syncStateStorage = leveldb.NewDiskStorage(syncStateProvider, m.logger.Named("sync-state"))

	// Create repositories
	m.fileRepo = fileRepo.ProvideRepository(m.localFilesStorage)
	m.collectionRepo = collectionRepo.ProvideRepository(m.localFilesStorage)
	m.syncStateRepo = syncstateRepo.ProvideRepository(m.syncStateStorage)

	m.currentUserEmail = userEmail

	m.logger.Info("Storage initialized successfully",
		zap.String("email_hash", config.GetEmailHashForPath(userEmail)))

	return nil
}

// Cleanup cleans up all user-specific storage.
// This should be called when a user logs out.
func (m *Manager) Cleanup() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.cleanupStorageUnsafe()
}

// cleanupStorageUnsafe cleans up storage without acquiring the lock.
// Caller must hold the lock.
func (m *Manager) cleanupStorageUnsafe() {
	if m.localFilesStorage != nil {
		if closer, ok := m.localFilesStorage.(interface{ Close() error }); ok {
			if err := closer.Close(); err != nil {
				m.logger.Warn("Failed to close local files storage", zap.Error(err))
			}
		}
		m.localFilesStorage = nil
	}

	if m.syncStateStorage != nil {
		if closer, ok := m.syncStateStorage.(interface{ Close() error }); ok {
			if err := closer.Close(); err != nil {
				m.logger.Warn("Failed to close sync state storage", zap.Error(err))
			}
		}
		m.syncStateStorage = nil
	}

	m.fileRepo = nil
	m.collectionRepo = nil
	m.syncStateRepo = nil
	m.currentUserEmail = ""

	m.logger.Debug("Storage cleaned up")
}

// IsInitialized returns true if storage has been initialized for a user.
func (m *Manager) IsInitialized() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.localFilesStorage != nil
}

// GetCurrentUserEmail returns the email of the user for whom storage is initialized.
// Returns empty string if no user storage is initialized.
func (m *Manager) GetCurrentUserEmail() string {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.currentUserEmail
}

// GetFileRepository returns the file repository for the current user.
// Returns nil if storage is not initialized.
func (m *Manager) GetFileRepository() fileDomain.Repository {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.fileRepo
}

// GetCollectionRepository returns the collection repository for the current user.
// Returns nil if storage is not initialized.
func (m *Manager) GetCollectionRepository() collectionDomain.Repository {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.collectionRepo
}

// GetSyncStateRepository returns the sync state repository for the current user.
// Returns nil if storage is not initialized.
func (m *Manager) GetSyncStateRepository() syncstateDomain.Repository {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.syncStateRepo
}

// GetLocalFilesStorage returns the raw local files storage for the current user.
// Returns nil if storage is not initialized.
func (m *Manager) GetLocalFilesStorage() storage.Storage {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.localFilesStorage
}

// DeleteUserData permanently deletes all local data for the specified user.
// This includes all files, metadata, and sync state stored on this device.
// IMPORTANT: This is a destructive operation and cannot be undone.
// The user will need to re-download all files from the cloud after this operation.
func (m *Manager) DeleteUserData(userEmail string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if userEmail == "" {
		return fmt.Errorf("user email is required")
	}

	emailHash := config.GetEmailHashForPath(userEmail)
	m.logger.Info("Deleting all local data for user",
		zap.String("email_hash", emailHash))

	// If this is the current user, clean up storage first
	if m.currentUserEmail == userEmail {
		m.cleanupStorageUnsafe()
	}

	// Get the user's data directory
	userDir, err := config.GetUserSpecificDataDir("maplefile", userEmail)
	if err != nil {
		m.logger.Error("Failed to get user data directory", zap.Error(err))
		return fmt.Errorf("failed to get user data directory: %w", err)
	}

	// Check if the directory exists
	if _, err := os.Stat(userDir); os.IsNotExist(err) {
		m.logger.Debug("User data directory does not exist, nothing to delete",
			zap.String("email_hash", emailHash))
		return nil
	}

	// Remove the entire user directory and all its contents
	if err := os.RemoveAll(userDir); err != nil {
		m.logger.Error("Failed to delete user data directory",
			zap.Error(err),
			zap.String("path", userDir))
		return fmt.Errorf("failed to delete user data: %w", err)
	}

	m.logger.Info("Successfully deleted all local data for user",
		zap.String("email_hash", emailHash))

	return nil
}

// GetUserDataSize returns the total size of local data stored for the specified user in bytes.
// Returns 0 if no data exists or if there's an error calculating the size.
func (m *Manager) GetUserDataSize(userEmail string) (int64, error) {
	if userEmail == "" {
		return 0, fmt.Errorf("user email is required")
	}

	userDir, err := config.GetUserSpecificDataDir("maplefile", userEmail)
	if err != nil {
		return 0, fmt.Errorf("failed to get user data directory: %w", err)
	}

	// Check if the directory exists
	if _, err := os.Stat(userDir); os.IsNotExist(err) {
		return 0, nil
	}

	var totalSize int64
	err = filepath.Walk(userDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil // Ignore errors and continue
		}
		if !info.IsDir() {
			totalSize += info.Size()
		}
		return nil
	})

	if err != nil {
		m.logger.Warn("Error calculating user data size", zap.Error(err))
		return totalSize, nil // Return what we have
	}

	return totalSize, nil
}
