// monorepo/cloud/backend/internal/maplefile/service/file/delete_multiple.go
package file

import (
	"context"
	"fmt"
	"time"

	"go.uber.org/zap"

	"github.com/gocql/gocql"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	dom_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
	uc_filemetadata "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata"
	uc_fileobjectstorage "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/fileobjectstorage"
	uc_storagedailyusage "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/storagedailyusage"
	uc_storageusageevent "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/storageusageevent"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/transaction"
)

type DeleteMultipleFilesRequestDTO struct {
	FileIDs []gocql.UUID `json:"file_ids"`
}

type DeleteMultipleFilesResponseDTO struct {
	Success        bool   `json:"success"`
	Message        string `json:"message"`
	DeletedCount   int    `json:"deleted_count"`
	SkippedCount   int    `json:"skipped_count"`
	TotalRequested int    `json:"total_requested"`
}

type DeleteMultipleFilesService interface {
	Execute(ctx context.Context, req *DeleteMultipleFilesRequestDTO) (*DeleteMultipleFilesResponseDTO, error)
}

type deleteMultipleFilesServiceImpl struct {
	config                    *config.Configuration
	logger                    *zap.Logger
	collectionRepo            dom_collection.CollectionRepository
	getMetadataByIDsUseCase   uc_filemetadata.GetFileMetadataByIDsUseCase
	deleteMetadataManyUseCase uc_filemetadata.DeleteManyFileMetadataUseCase
	deleteMultipleDataUseCase uc_fileobjectstorage.DeleteMultipleEncryptedDataUseCase
	// Add storage usage tracking use cases
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase
	updateStorageUsageUseCase      uc_storagedailyusage.UpdateStorageUsageUseCase
}

func NewDeleteMultipleFilesService(
	config *config.Configuration,
	logger *zap.Logger,
	collectionRepo dom_collection.CollectionRepository,
	getMetadataByIDsUseCase uc_filemetadata.GetFileMetadataByIDsUseCase,
	deleteMetadataManyUseCase uc_filemetadata.DeleteManyFileMetadataUseCase,
	deleteMultipleDataUseCase uc_fileobjectstorage.DeleteMultipleEncryptedDataUseCase,
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase,
	updateStorageUsageUseCase uc_storagedailyusage.UpdateStorageUsageUseCase,
) DeleteMultipleFilesService {
	logger = logger.Named("DeleteMultipleFilesService")
	return &deleteMultipleFilesServiceImpl{
		config:                         config,
		logger:                         logger,
		collectionRepo:                 collectionRepo,
		getMetadataByIDsUseCase:        getMetadataByIDsUseCase,
		deleteMetadataManyUseCase:      deleteMetadataManyUseCase,
		deleteMultipleDataUseCase:      deleteMultipleDataUseCase,
		createStorageUsageEventUseCase: createStorageUsageEventUseCase,
		updateStorageUsageUseCase:      updateStorageUsageUseCase,
	}
}

func (svc *deleteMultipleFilesServiceImpl) Execute(ctx context.Context, req *DeleteMultipleFilesRequestDTO) (*DeleteMultipleFilesResponseDTO, error) {
	//
	// STEP 1: Validation
	//
	if req == nil {
		svc.logger.Warn("Failed validation with nil request")
		return nil, httperror.NewForBadRequestWithSingleField("non_field_error", "File IDs are required")
	}

	if req.FileIDs == nil || len(req.FileIDs) == 0 {
		svc.logger.Warn("Empty file IDs provided")
		return nil, httperror.NewForBadRequestWithSingleField("file_ids", "File IDs are required")
	}

	// Validate individual file IDs
	e := make(map[string]string)
	for i, fileID := range req.FileIDs {
		if fileID.String() == "" {
			e[fmt.Sprintf("file_ids[%d]", i)] = "File ID is required"
		}
	}
	if len(e) != 0 {
		svc.logger.Warn("Failed validation",
			zap.Any("error", e))
		return nil, httperror.NewForBadRequest(&e)
	}

	//
	// STEP 2: 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 3: Get file metadata for all files
	//
	files, err := svc.getMetadataByIDsUseCase.Execute(req.FileIDs)
	if err != nil {
		svc.logger.Error("Failed to get file metadata",
			zap.Any("error", err),
			zap.Any("file_ids", req.FileIDs))
		return nil, err
	}

	//
	// STEP 4: Group files by collection to optimize permission checks
	//
	filesByCollection := make(map[gocql.UUID][]*dom_file.File)
	for _, file := range files {
		filesByCollection[file.CollectionID] = append(filesByCollection[file.CollectionID], file)
	}

	//
	// STEP 5: Pre-fetch collection access permissions (eliminates N+1 query)
	//
	collectionAccess := make(map[gocql.UUID]bool)
	for collectionID := range filesByCollection {
		hasAccess, err := svc.collectionRepo.CheckAccess(ctx, collectionID, userID, dom_collection.CollectionPermissionReadWrite)
		if err != nil {
			svc.logger.Warn("Failed to check access for collection",
				zap.Any("error", err),
				zap.Any("collection_id", collectionID))
			collectionAccess[collectionID] = false
			continue
		}
		collectionAccess[collectionID] = hasAccess
	}

	//
	// STEP 6: Filter files that the user has permission to delete and track storage by owner
	//
	var deletableFiles []*dom_file.File
	var storagePaths []string
	skippedCount := 0
	storageByOwner := make(map[gocql.UUID]int64) // Track total storage to release per owner
	filesPerCollection := make(map[gocql.UUID]int) // Track files to delete per collection for count updates

	for _, file := range files {
		// Use pre-fetched access permission
		hasAccess := collectionAccess[file.CollectionID]

		if !hasAccess {
			svc.logger.Warn("User doesn't have permission to delete file, skipping",
				zap.Any("user_id", userID),
				zap.Any("file_id", file.ID),
				zap.Any("collection_id", file.CollectionID))
			skippedCount++
			continue
		}

		// Check valid transitions.
		if err := dom_collection.IsValidStateTransition(file.State, dom_file.FileStateDeleted); err != nil {
			svc.logger.Warn("Invalid file state transition",
				zap.Any("user_id", userID),
				zap.Error(err))
			skippedCount++
			continue
		}

		deletableFiles = append(deletableFiles, file)
		storagePaths = append(storagePaths, file.EncryptedFileObjectKey)

		// Add thumbnail paths if they exist
		if file.EncryptedThumbnailObjectKey != "" {
			storagePaths = append(storagePaths, file.EncryptedThumbnailObjectKey)
		}

		// Track storage by owner for active files
		if file.State == dom_file.FileStateActive {
			totalFileSize := file.EncryptedFileSizeInBytes + file.EncryptedThumbnailSizeInBytes
			storageByOwner[file.OwnerID] += totalFileSize
			// Track files per collection for count updates
			filesPerCollection[file.CollectionID]++
		}
	}

	if len(deletableFiles) == 0 {
		return &DeleteMultipleFilesResponseDTO{
			Success:        true,
			Message:        "No files could be deleted due to permission restrictions",
			DeletedCount:   0,
			SkippedCount:   len(req.FileIDs),
			TotalRequested: len(req.FileIDs),
		}, nil
	}

	//
	// SAGA: Initialize distributed transaction manager
	//
	saga := transaction.NewSaga("delete-multiple-files", svc.logger)

	svc.logger.Info("Starting multiple file deletion with SAGA protection",
		zap.Int("deletable_files_count", len(deletableFiles)),
		zap.Int("skipped_count", skippedCount),
		zap.Int("total_requested", len(req.FileIDs)))

	// Note: Version tracking is not needed for hard delete since the file is being
	// completely removed. Version tracking is handled in SoftDeleteFileService for
	// soft deletes where tombstone records are maintained.

	//
	// STEP 7: Delete file metadata (SAGA protected)
	//
	deletableFileIDs := make([]gocql.UUID, len(deletableFiles))
	deletableFilesCaptured := make([]*dom_file.File, len(deletableFiles))
	for i, file := range deletableFiles {
		deletableFileIDs[i] = file.ID
		// Deep copy for compensation
		fileCopy := *file
		deletableFilesCaptured[i] = &fileCopy
	}

	err = svc.deleteMetadataManyUseCase.Execute(deletableFileIDs)
	if err != nil {
		svc.logger.Error("Failed to delete file metadata",
			zap.Error(err),
			zap.Int("file_count", len(deletableFileIDs)))
		saga.Rollback(ctx)
		return nil, err
	}

	// Register compensation: This is a hard delete, so we cannot easily restore
	// The compensation logs the failure - manual intervention may be required
	saga.AddCompensation(func(ctx context.Context) error {
		svc.logger.Warn("SAGA compensation: hard delete cannot be automatically reversed",
			zap.Int("deleted_file_count", len(deletableFilesCaptured)),
			zap.String("note", "Manual restoration from backup may be required"))
		// For hard delete, we can't restore deleted metadata without backup
		// This compensation serves as an audit trail
		return nil
	})

	//
	// STEP 8: Update file counts for affected collections (SAGA protected)
	//
	for collectionID, fileCount := range filesPerCollection {
		if fileCount > 0 {
			// Decrement the file count for this collection
			for i := 0; i < fileCount; i++ {
				err = svc.collectionRepo.DecrementFileCount(ctx, collectionID)
				if err != nil {
					svc.logger.Error("Failed to decrement file count for collection",
						zap.String("collection_id", collectionID.String()),
						zap.Int("file_count", fileCount),
						zap.Error(err))
					saga.Rollback(ctx)
					return nil, err
				}
			}

			// Register compensation: increment the count back
			collectionIDCaptured := collectionID
			fileCountCaptured := fileCount
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: restoring file count",
					zap.String("collection_id", collectionIDCaptured.String()),
					zap.Int("file_count", fileCountCaptured))
				for i := 0; i < fileCountCaptured; i++ {
					if err := svc.collectionRepo.IncrementFileCount(ctx, collectionIDCaptured); err != nil {
						svc.logger.Error("Failed to restore file count during compensation",
							zap.String("collection_id", collectionIDCaptured.String()),
							zap.Error(err))
						return err
					}
				}
				return nil
			})
		}
	}

	//
	// STEP 9: Create storage usage events and update daily usage for each owner (SAGA protected)
	//
	today := time.Now().Truncate(24 * time.Hour)
	for ownerID, totalSize := range storageByOwner {
		if totalSize > 0 {
			// Create storage usage event (SAGA protected)
			err = svc.createStorageUsageEventUseCase.Execute(ctx, ownerID, totalSize, "remove")
			if err != nil {
				svc.logger.Error("Failed to create storage usage event for bulk deletion",
					zap.String("owner_id", ownerID.String()),
					zap.Int64("total_size", totalSize),
					zap.Error(err))
				saga.Rollback(ctx)
				return nil, err
			}

			// Register compensation: create compensating "add" event
			ownerIDCaptured := ownerID
			totalSizeCaptured := totalSize
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: creating compensating usage event",
					zap.String("owner_id", ownerIDCaptured.String()))
				return svc.createStorageUsageEventUseCase.Execute(ctx, ownerIDCaptured, totalSizeCaptured, "add")
			})

			// Update daily storage usage (SAGA protected)
			updateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
				UserID:      ownerID,
				UsageDay:    &today,
				TotalBytes:  -totalSize, // Negative because we're removing
				AddBytes:    0,
				RemoveBytes: totalSize,
				IsIncrement: true, // Increment the existing values
			}
			err = svc.updateStorageUsageUseCase.Execute(ctx, updateReq)
			if err != nil {
				svc.logger.Error("Failed to update daily storage usage for bulk deletion",
					zap.String("owner_id", ownerID.String()),
					zap.Int64("total_size", totalSize),
					zap.Error(err))
				saga.Rollback(ctx)
				return nil, err
			}

			// Register compensation: reverse the usage update
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: reversing daily usage update",
					zap.String("owner_id", ownerIDCaptured.String()))
				compensateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
					UserID:      ownerIDCaptured,
					UsageDay:    &today,
					TotalBytes:  totalSizeCaptured, // Positive to reverse
					AddBytes:    totalSizeCaptured,
					RemoveBytes: 0,
					IsIncrement: true,
				}
				return svc.updateStorageUsageUseCase.Execute(ctx, compensateReq)
			})
		}
	}

	//
	// STEP 10: Delete S3 objects
	//
	if len(storagePaths) > 0 {
		svc.logger.Info("Deleting S3 objects for multiple files",
			zap.Int("s3_objects_count", len(storagePaths)))

		if err := svc.deleteMultipleDataUseCase.Execute(storagePaths); err != nil {
			// Log but don't fail - S3 deletion is best effort after metadata is deleted
			svc.logger.Error("Failed to delete some S3 objects (continuing anyway)",
				zap.Error(err),
				zap.Int("s3_objects_count", len(storagePaths)))
		} else {
			svc.logger.Info("Successfully deleted all S3 objects",
				zap.Int("s3_objects_deleted", len(storagePaths)))
		}
	}

	//
	// SUCCESS: All operations completed with SAGA protection
	//
	svc.logger.Info("Multiple files deleted successfully with SAGA protection",
		zap.Int("deleted_count", len(deletableFiles)),
		zap.Int("skipped_count", skippedCount),
		zap.Int("total_requested", len(req.FileIDs)),
		zap.String("user_id", userID.String()),
		zap.Int("affected_owners", len(storageByOwner)),
		zap.Int("s3_objects_deleted", len(storagePaths)))

	return &DeleteMultipleFilesResponseDTO{
		Success:        true,
		Message:        fmt.Sprintf("Successfully deleted %d files", len(deletableFiles)),
		DeletedCount:   len(deletableFiles),
		SkippedCount:   skippedCount,
		TotalRequested: len(req.FileIDs),
	}, nil
}
