// cloud/maplefile-backend/internal/maplefile/service/dashboard/get_dashboard.go
package dashboard

import (
	"context"
	"encoding/base64"
	"math"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
	dom_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/user"
	file_service "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
	uc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/collection"
	uc_filemetadata "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata"
	uc_storagedailyusage "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/storagedailyusage"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"github.com/gocql/gocql"
)

type GetDashboardService interface {
	Execute(ctx context.Context) (*GetDashboardResponseDTO, error)
}

type getDashboardServiceImpl struct {
	config                  *config.Configuration
	logger                  *zap.Logger
	listRecentFilesService  file_service.ListRecentFilesService
	userGetByIDUseCase      uc_user.UserGetByIDUseCase
	countUserFilesUseCase   uc_filemetadata.CountUserFilesUseCase
	countUserFoldersUseCase uc_collection.CountUserFoldersUseCase
	getStorageTrendUseCase  uc_storagedailyusage.GetStorageDailyUsageTrendUseCase
	getCollectionUseCase    uc_collection.GetCollectionUseCase
}

func NewGetDashboardService(
	config *config.Configuration,
	logger *zap.Logger,
	listRecentFilesService file_service.ListRecentFilesService,
	userGetByIDUseCase uc_user.UserGetByIDUseCase,
	countUserFilesUseCase uc_filemetadata.CountUserFilesUseCase,
	countUserFoldersUseCase uc_collection.CountUserFoldersUseCase,
	getStorageTrendUseCase uc_storagedailyusage.GetStorageDailyUsageTrendUseCase,
	getCollectionUseCase uc_collection.GetCollectionUseCase,
) GetDashboardService {
	logger = logger.Named("GetDashboardService")
	return &getDashboardServiceImpl{
		config:                  config,
		logger:                  logger,
		listRecentFilesService:  listRecentFilesService,
		userGetByIDUseCase:      userGetByIDUseCase,
		countUserFilesUseCase:   countUserFilesUseCase,
		countUserFoldersUseCase: countUserFoldersUseCase,
		getStorageTrendUseCase:  getStorageTrendUseCase,
		getCollectionUseCase:    getCollectionUseCase,
	}
}

func (svc *getDashboardServiceImpl) Execute(ctx context.Context) (*GetDashboardResponseDTO, error) {
	//
	// STEP 1: Get user ID from context
	//
	userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID)
	if !ok {
		svc.logger.Error("Failed getting user ID from context")
		return nil, httperror.NewForInternalServerErrorWithSingleField("message", "Authentication context error")
	}

	//
	// STEP 2: Validation
	//
	e := make(map[string]string)
	if userID.String() == "" {
		e["user_id"] = "User ID is required"
	}
	if len(e) != 0 {
		svc.logger.Warn("Failed validating get dashboard",
			zap.Any("error", e))
		return nil, httperror.NewForBadRequest(&e)
	}

	//
	// STEP 3: Get user information for storage data
	//
	user, err := svc.userGetByIDUseCase.Execute(ctx, userID)
	if err != nil {
		svc.logger.Error("Failed to get user for dashboard",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, err
	}

	if user == nil {
		svc.logger.Warn("User not found for dashboard",
			zap.String("user_id", userID.String()))
		return nil, httperror.NewForNotFoundWithSingleField("user_id", "User not found")
	}

	//
	// STEP 4: Get file count
	//
	fileCountResp, err := svc.countUserFilesUseCase.Execute(ctx, userID)
	if err != nil {
		svc.logger.Error("Failed to count user files for dashboard",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, err
	}

	//
	// STEP 5: Get folder count (folders only, not albums)
	//
	folderCountResp, err := svc.countUserFoldersUseCase.Execute(ctx, userID)
	if err != nil {
		svc.logger.Error("Failed to count user folders for dashboard",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, err
	}

	// Debug logging for folder count
	svc.logger.Debug("Folder count debug info",
		zap.String("user_id", userID.String()),
		zap.Int("total_folders_returned", folderCountResp.TotalFolders))

	//
	// STEP 6: Get storage usage trend (last 7 days)
	//
	trendReq := &uc_storagedailyusage.GetStorageDailyUsageTrendRequest{
		UserID:      userID,
		TrendPeriod: "7days",
	}

	storageTrend, err := svc.getStorageTrendUseCase.Execute(ctx, trendReq)
	if err != nil {
		svc.logger.Warn("Failed to get storage trend for dashboard, using empty trend",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		// Don't fail the entire dashboard for trend data
		storageTrend = nil
	}

	//
	// STEP 7: Get recent files using the working Recent Files Service
	//
	var recentFiles []file_service.RecentFileResponseDTO
	recentFilesResp, err := svc.listRecentFilesService.Execute(ctx, nil, 5)
	if err != nil {
		svc.logger.Warn("Failed to get recent files for dashboard, using empty list",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		// Don't fail the entire dashboard for recent files
		recentFiles = []file_service.RecentFileResponseDTO{}
	} else {
		recentFiles = recentFilesResp.Files
	}

	//
	// STEP 8: Fetch collection keys for recent files
	// This allows clients to decrypt file metadata without making additional API calls
	//
	collectionKeys := svc.fetchCollectionKeysForFiles(ctx, recentFiles)

	//
	// STEP 9: Build dashboard response
	//
	dashboard := &DashboardDataDTO{
		Summary:           svc.buildSummary(user, fileCountResp.TotalFiles, folderCountResp.TotalFolders, storageTrend), // Pass storageTrend to calculate actual storage
		StorageUsageTrend: svc.buildStorageUsageTrend(storageTrend),
		RecentFiles:       recentFiles,
		CollectionKeys:    collectionKeys,
	}

	response := &GetDashboardResponseDTO{
		Dashboard: dashboard,
		Success:   true,
		Message:   "Dashboard data retrieved successfully",
	}

	svc.logger.Info("Dashboard data retrieved successfully",
		zap.String("user_id", userID.String()),
		zap.Int("total_files", fileCountResp.TotalFiles),
		zap.Int("total_folders", folderCountResp.TotalFolders), // CHANGED: Use TotalFolders
		zap.Int("recent_files_count", len(recentFiles)))

	return response, nil
}

func (svc *getDashboardServiceImpl) buildSummary(user *dom_user.User, totalFiles, totalFolders int, storageTrend *storagedailyusage.StorageUsageTrend) SummaryDTO {
	// Calculate storage from the most recent daily usage data
	var storageUsedBytes int64 = 0

	// Debug logging for storage trend
	if storageTrend != nil {
		svc.logger.Debug("Storage trend received in buildSummary",
			zap.Int("daily_usages_count", len(storageTrend.DailyUsages)),
			zap.Int64("total_added", storageTrend.TotalAdded),
			zap.Int64("net_change", storageTrend.NetChange))

		if len(storageTrend.DailyUsages) > 0 {
			// Get the most recent day's total bytes (last element in the sorted array)
			mostRecentDay := storageTrend.DailyUsages[len(storageTrend.DailyUsages)-1]
			storageUsedBytes = mostRecentDay.TotalBytes

			// BUGFIX: Ensure storage never goes negative
			// This can happen if deletion events exceed actual storage (edge case with storage tracking)
			if storageUsedBytes < 0 {
				svc.logger.Warn("Storage used bytes is negative, resetting to 0",
					zap.Int64("negative_value", storageUsedBytes),
					zap.Time("usage_day", mostRecentDay.UsageDay))
				storageUsedBytes = 0
			}

			svc.logger.Debug("Using storage from most recent day",
				zap.Time("usage_day", mostRecentDay.UsageDay),
				zap.Int64("total_bytes", mostRecentDay.TotalBytes),
				zap.Int64("total_add_bytes", mostRecentDay.TotalAddBytes),
				zap.Int64("total_remove_bytes", mostRecentDay.TotalRemoveBytes))
		} else {
			svc.logger.Debug("No daily usage entries found in storage trend")
		}
	} else {
		svc.logger.Debug("Storage trend is nil")
	}

	var storageLimitBytes int64 = 10 * 1024 * 1024 * 1024 // 10GB default limit

	// Convert storage used to human-readable format
	storageUsed := svc.convertBytesToStorageAmount(storageUsedBytes)
	storageLimit := svc.convertBytesToStorageAmount(storageLimitBytes)

	// Calculate storage percentage with proper rounding
	storagePercentage := 0
	if storageLimitBytes > 0 {
		percentage := (float64(storageUsedBytes) / float64(storageLimitBytes)) * 100

		// Use math.Round for proper rounding instead of truncation
		storagePercentage = int(math.Round(percentage))

		// If there's actual usage but percentage rounds to 0, show at least 1%
		if storagePercentage == 0 && storageUsedBytes > 0 {
			storagePercentage = 1
		}
	}

	// Debug logging for storage calculation
	svc.logger.Debug("Storage calculation debug",
		zap.Int64("storage_used_bytes", storageUsedBytes),
		zap.Int64("storage_limit_bytes", storageLimitBytes),
		zap.Int("calculated_percentage", storagePercentage))

	return SummaryDTO{
		TotalFiles:             totalFiles,
		TotalFolders:           totalFolders, // Now this will be actual folders only
		StorageUsed:            storageUsed,
		StorageLimit:           storageLimit,
		StorageUsagePercentage: storagePercentage,
	}
}

func (svc *getDashboardServiceImpl) buildStorageUsageTrend(trend *storagedailyusage.StorageUsageTrend) StorageUsageTrendDTO {
	if trend == nil || len(trend.DailyUsages) == 0 {
		return StorageUsageTrendDTO{
			Period:     "Last 7 days",
			DataPoints: []DataPointDTO{},
		}
	}

	dataPoints := make([]DataPointDTO, len(trend.DailyUsages))
	for i, daily := range trend.DailyUsages {
		dataPoints[i] = DataPointDTO{
			Date:  daily.UsageDay.Format("2006-01-02"),
			Usage: svc.convertBytesToStorageAmount(daily.TotalBytes),
		}
	}

	return StorageUsageTrendDTO{
		Period:     "Last 7 days",
		DataPoints: dataPoints,
	}
}

func (svc *getDashboardServiceImpl) convertBytesToStorageAmount(bytes int64) StorageAmountDTO {
	const (
		KB = 1024
		MB = KB * 1024
		GB = MB * 1024
		TB = GB * 1024
	)

	switch {
	case bytes >= TB:
		return StorageAmountDTO{
			Value: float64(bytes) / TB,
			Unit:  "TB",
		}
	case bytes >= GB:
		return StorageAmountDTO{
			Value: float64(bytes) / GB,
			Unit:  "GB",
		}
	case bytes >= MB:
		return StorageAmountDTO{
			Value: float64(bytes) / MB,
			Unit:  "MB",
		}
	case bytes >= KB:
		return StorageAmountDTO{
			Value: float64(bytes) / KB,
			Unit:  "KB",
		}
	default:
		return StorageAmountDTO{
			Value: float64(bytes),
			Unit:  "B",
		}
	}
}

// fetchCollectionKeysForFiles fetches the encrypted collection keys for the collections
// referenced by the recent files. This allows clients to decrypt file metadata without
// making additional API calls for each collection.
func (svc *getDashboardServiceImpl) fetchCollectionKeysForFiles(ctx context.Context, files []file_service.RecentFileResponseDTO) []CollectionKeyDTO {
	if len(files) == 0 {
		return nil
	}

	// Collect unique collection IDs from the files
	collectionIDSet := make(map[string]gocql.UUID)
	for _, f := range files {
		collectionIDStr := f.CollectionID.String()
		if _, exists := collectionIDSet[collectionIDStr]; !exists {
			collectionIDSet[collectionIDStr] = f.CollectionID
		}
	}

	// Fetch each unique collection and extract its encrypted key
	collectionKeys := make([]CollectionKeyDTO, 0, len(collectionIDSet))
	for collectionIDStr, collectionID := range collectionIDSet {
		collection, err := svc.getCollectionUseCase.Execute(ctx, collectionID)
		if err != nil {
			svc.logger.Warn("Failed to fetch collection for dashboard collection keys",
				zap.String("collection_id", collectionIDStr),
				zap.Error(err))
			continue
		}

		if collection == nil {
			svc.logger.Warn("Collection not found for dashboard collection keys",
				zap.String("collection_id", collectionIDStr))
			continue
		}

		// Only include if we have the encrypted collection key
		if collection.EncryptedCollectionKey != nil && len(collection.EncryptedCollectionKey.Ciphertext) > 0 {
			collectionKeys = append(collectionKeys, CollectionKeyDTO{
				CollectionID:                collectionIDStr,
				EncryptedCollectionKey:      base64.StdEncoding.EncodeToString(collection.EncryptedCollectionKey.Ciphertext),
				EncryptedCollectionKeyNonce: base64.StdEncoding.EncodeToString(collection.EncryptedCollectionKey.Nonce),
			})
		}
	}

	svc.logger.Debug("Fetched collection keys for dashboard",
		zap.Int("unique_collections", len(collectionIDSet)),
		zap.Int("keys_returned", len(collectionKeys)))

	return collectionKeys
}
