// monorepo/cloud/backend/internal/maplefile/service/collection/softdelete.go
package collection

import (
	"context"
	"time"

	"go.uber.org/zap"

	"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_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/collection"
	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"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/transaction"
	"github.com/gocql/gocql"
)

type SoftDeleteCollectionRequestDTO struct {
	ID              gocql.UUID `json:"id"`
	ForceHardDelete bool       `json:"force_hard_delete"` // Skip tombstone for GDPR right-to-be-forgotten
}

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

type SoftDeleteCollectionService interface {
	Execute(ctx context.Context, req *SoftDeleteCollectionRequestDTO) (*SoftDeleteCollectionResponseDTO, error)
}

type softDeleteCollectionServiceImpl struct {
	config                         *config.Configuration
	logger                         *zap.Logger
	repo                           dom_collection.CollectionRepository
	fileRepo                       dom_file.FileMetadataRepository
	getCollectionUseCase           uc_collection.GetCollectionUseCase
	updateCollectionUseCase        uc_collection.UpdateCollectionUseCase
	hardDeleteCollectionUseCase    uc_collection.HardDeleteCollectionUseCase
	deleteMultipleDataUseCase      uc_fileobjectstorage.DeleteMultipleEncryptedDataUseCase
	// Storage quota management
	storageQuotaHelperUseCase      uc_user.UserStorageQuotaHelperUseCase
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase
	updateStorageUsageUseCase      uc_storagedailyusage.UpdateStorageUsageUseCase
}

func NewSoftDeleteCollectionService(
	config *config.Configuration,
	logger *zap.Logger,
	repo dom_collection.CollectionRepository,
	fileRepo dom_file.FileMetadataRepository,
	getCollectionUseCase uc_collection.GetCollectionUseCase,
	updateCollectionUseCase uc_collection.UpdateCollectionUseCase,
	hardDeleteCollectionUseCase uc_collection.HardDeleteCollectionUseCase,
	deleteMultipleDataUseCase uc_fileobjectstorage.DeleteMultipleEncryptedDataUseCase,
	storageQuotaHelperUseCase uc_user.UserStorageQuotaHelperUseCase,
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase,
	updateStorageUsageUseCase uc_storagedailyusage.UpdateStorageUsageUseCase,
) SoftDeleteCollectionService {
	logger = logger.Named("SoftDeleteCollectionService")
	return &softDeleteCollectionServiceImpl{
		config:                         config,
		logger:                         logger,
		repo:                           repo,
		fileRepo:                       fileRepo,
		getCollectionUseCase:           getCollectionUseCase,
		updateCollectionUseCase:        updateCollectionUseCase,
		hardDeleteCollectionUseCase:    hardDeleteCollectionUseCase,
		deleteMultipleDataUseCase:      deleteMultipleDataUseCase,
		storageQuotaHelperUseCase:      storageQuotaHelperUseCase,
		createStorageUsageEventUseCase: createStorageUsageEventUseCase,
		updateStorageUsageUseCase:      updateStorageUsageUseCase,
	}
}

func (svc *softDeleteCollectionServiceImpl) Execute(ctx context.Context, req *SoftDeleteCollectionRequestDTO) (*SoftDeleteCollectionResponseDTO, error) {
	//
	// STEP 1: Validation
	//
	if req == nil {
		svc.logger.Warn("Failed validation with nil request")
		return nil, httperror.NewForBadRequestWithSingleField("non_field_error", "Collection ID is required")
	}

	if req.ID.String() == "" {
		svc.logger.Warn("Empty collection ID")
		return nil, httperror.NewForBadRequestWithSingleField("id", "Collection ID is required")
	}

	//
	// 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: Retrieve related records
	//
	collection, err := svc.getCollectionUseCase.Execute(ctx, req.ID)
	if err != nil {
		svc.logger.Error("Failed to get collection",
			zap.Any("error", err),
			zap.Any("collection_id", req.ID))
		return nil, err
	}

	if collection == nil {
		svc.logger.Debug("Collection not found",
			zap.Any("collection_id", req.ID))
		return nil, httperror.NewForNotFoundWithSingleField("message", "Collection not found")
	}

	//
	// STEP 4: Check if user has rights to delete this collection
	//
	if collection.OwnerID != userID {
		svc.logger.Warn("Unauthorized collection deletion attempt",
			zap.Any("user_id", userID),
			zap.Any("collection_id", req.ID))
		return nil, httperror.NewForForbiddenWithSingleField("message", "Only the collection owner can delete a collection")
	}

	// Check valid transitions.
	if err := dom_collection.IsValidStateTransition(collection.State, dom_collection.CollectionStateDeleted); err != nil {
		svc.logger.Warn("Invalid collection state transition",
			zap.Any("user_id", userID),
			zap.Error(err))
		return nil, err
	}

	svc.logger.Info("Starting soft delete of collection hierarchy",
		zap.String("collection_id", collection.ID.String()),
		zap.Int("member_count", len(collection.Members)))

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

	//
	// STEP 5: Find all descendant collections
	//
	descendants, err := svc.repo.FindDescendants(ctx, req.ID)
	if err != nil {
		svc.logger.Error("Failed to check for descendant collections",
			zap.Any("error", err),
			zap.Any("collection_id", req.ID))
		return nil, err
	}

	svc.logger.Info("Found descendant collections for deletion",
		zap.Any("collection_id", req.ID),
		zap.Int("descendants_count", len(descendants)))

	//
	// STEP 6: Delete all files in the parent collection
	//
	parentFiles, err := svc.fileRepo.GetByCollection(req.ID)
	if err != nil {
		svc.logger.Error("Failed to get files for parent collection",
			zap.Any("error", err),
			zap.Any("collection_id", req.ID))
		return nil, err
	}

	// Collect all S3 storage paths to delete and calculate total storage to release
	var allStoragePaths []string
	var totalStorageToRelease int64 = 0

	if len(parentFiles) > 0 {
		parentFileIDs := make([]gocql.UUID, len(parentFiles))
		for i, file := range parentFiles {
			parentFileIDs[i] = file.ID
			// Collect S3 paths for deletion
			allStoragePaths = append(allStoragePaths, file.EncryptedFileObjectKey)
			if file.EncryptedThumbnailObjectKey != "" {
				allStoragePaths = append(allStoragePaths, file.EncryptedThumbnailObjectKey)
			}
			// Calculate storage to release (only for active files)
			if file.State == dom_file.FileStateActive {
				totalStorageToRelease += file.EncryptedFileSizeInBytes + file.EncryptedThumbnailSizeInBytes
			}
		}

		// Execute parent file deletion (hard or soft based on flag)
		if req.ForceHardDelete {
			svc.logger.Info("Hard deleting parent collection files (GDPR mode)",
				zap.Int("file_count", len(parentFileIDs)))
			if err := svc.fileRepo.HardDeleteMany(parentFileIDs); err != nil {
				svc.logger.Error("Failed to hard-delete files in parent collection",
					zap.Any("error", err),
					zap.Any("collection_id", req.ID),
					zap.Int("file_count", len(parentFileIDs)))
				saga.Rollback(ctx)
				return nil, err
			}
			// No compensation for hard delete - GDPR requires permanent deletion
		} else {
			if err := svc.fileRepo.SoftDeleteMany(parentFileIDs); err != nil {
				svc.logger.Error("Failed to soft-delete files in parent collection",
					zap.Any("error", err),
					zap.Any("collection_id", req.ID),
					zap.Int("file_count", len(parentFileIDs)))
				saga.Rollback(ctx) // Rollback any previous operations
				return nil, err
			}

			// SAGA: Register compensation for parent files deletion
			// IMPORTANT: Capture parentFileIDs by value for closure
			parentFileIDsCaptured := parentFileIDs
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: restoring parent collection files",
					zap.String("collection_id", req.ID.String()),
					zap.Int("file_count", len(parentFileIDsCaptured)))
				return svc.fileRepo.RestoreMany(parentFileIDsCaptured)
			})
		}

		svc.logger.Info("Deleted files in parent collection",
			zap.Any("collection_id", req.ID),
			zap.Int("file_count", len(parentFileIDs)))
	}

	//
	// STEP 7: Delete all files in descendant collections
	//
	totalDescendantFiles := 0
	for _, descendant := range descendants {
		descendantFiles, err := svc.fileRepo.GetByCollection(descendant.ID)
		if err != nil {
			svc.logger.Error("Failed to get files for descendant collection",
				zap.Any("error", err),
				zap.Any("descendant_id", descendant.ID))
			saga.Rollback(ctx) // Rollback all previous operations
			return nil, err
		}

		if len(descendantFiles) > 0 {
			descendantFileIDs := make([]gocql.UUID, len(descendantFiles))
			for i, file := range descendantFiles {
				descendantFileIDs[i] = file.ID
				// Collect S3 paths for deletion
				allStoragePaths = append(allStoragePaths, file.EncryptedFileObjectKey)
				if file.EncryptedThumbnailObjectKey != "" {
					allStoragePaths = append(allStoragePaths, file.EncryptedThumbnailObjectKey)
				}
				// Calculate storage to release (only for active files)
				if file.State == dom_file.FileStateActive {
					totalStorageToRelease += file.EncryptedFileSizeInBytes + file.EncryptedThumbnailSizeInBytes
				}
			}

			// Execute descendant file deletion (hard or soft based on flag)
			if req.ForceHardDelete {
				if err := svc.fileRepo.HardDeleteMany(descendantFileIDs); err != nil {
					svc.logger.Error("Failed to hard-delete files in descendant collection",
						zap.Any("error", err),
						zap.Any("descendant_id", descendant.ID),
						zap.Int("file_count", len(descendantFileIDs)))
					saga.Rollback(ctx)
					return nil, err
				}
				// No compensation for hard delete - GDPR requires permanent deletion
			} else {
				if err := svc.fileRepo.SoftDeleteMany(descendantFileIDs); err != nil {
					svc.logger.Error("Failed to soft-delete files in descendant collection",
						zap.Any("error", err),
						zap.Any("descendant_id", descendant.ID),
						zap.Int("file_count", len(descendantFileIDs)))
					saga.Rollback(ctx) // Rollback all previous operations
					return nil, err
				}

				// SAGA: Register compensation for this batch of descendant files
				// IMPORTANT: Capture by value for closure
				descendantFileIDsCaptured := descendantFileIDs
				descendantIDCaptured := descendant.ID
				saga.AddCompensation(func(ctx context.Context) error {
					svc.logger.Warn("SAGA compensation: restoring descendant collection files",
						zap.String("descendant_id", descendantIDCaptured.String()),
						zap.Int("file_count", len(descendantFileIDsCaptured)))
					return svc.fileRepo.RestoreMany(descendantFileIDsCaptured)
				})
			}

			totalDescendantFiles += len(descendantFileIDs)
			svc.logger.Debug("Deleted files in descendant collection",
				zap.Any("descendant_id", descendant.ID),
				zap.Int("file_count", len(descendantFileIDs)))
		}
	}

	svc.logger.Info("Soft-deleted all files in descendant collections",
		zap.Int("total_descendant_files", totalDescendantFiles),
		zap.Int("descendants_count", len(descendants)))

	//
	// STEP 8: Delete all descendant collections
	//
	for _, descendant := range descendants {
		// Execute descendant collection deletion (hard or soft based on flag)
		if req.ForceHardDelete {
			if err := svc.hardDeleteCollectionUseCase.Execute(ctx, descendant.ID); err != nil {
				svc.logger.Error("Failed to hard-delete descendant collection",
					zap.Any("error", err),
					zap.Any("descendant_id", descendant.ID))
				saga.Rollback(ctx)
				return nil, err
			}
			// No compensation for hard delete - GDPR requires permanent deletion
		} else {
			if err := svc.repo.SoftDelete(ctx, descendant.ID); err != nil {
				svc.logger.Error("Failed to soft-delete descendant collection",
					zap.Any("error", err),
					zap.Any("descendant_id", descendant.ID))
				saga.Rollback(ctx) // Rollback all previous operations
				return nil, err
			}

			// SAGA: Register compensation for this descendant collection
			// IMPORTANT: Capture by value for closure
			descendantIDCaptured := descendant.ID
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: restoring descendant collection",
					zap.String("descendant_id", descendantIDCaptured.String()))
				return svc.repo.Restore(ctx, descendantIDCaptured)
			})
		}

		svc.logger.Debug("Deleted descendant collection",
			zap.Any("descendant_id", descendant.ID),
			zap.String("descendant_name", descendant.EncryptedName))
	}

	svc.logger.Info("Deleted all descendant collections",
		zap.Int("descendants_count", len(descendants)))

	//
	// STEP 9: Finally, delete the parent collection
	//
	if req.ForceHardDelete {
		svc.logger.Info("Hard deleting parent collection (GDPR mode)",
			zap.String("collection_id", req.ID.String()))
		if err := svc.hardDeleteCollectionUseCase.Execute(ctx, req.ID); err != nil {
			svc.logger.Error("Failed to hard-delete parent collection",
				zap.Any("error", err),
				zap.Any("collection_id", req.ID))
			saga.Rollback(ctx)
			return nil, err
		}
		// No compensation for hard delete - GDPR requires permanent deletion
	} else {
		if err := svc.repo.SoftDelete(ctx, req.ID); err != nil {
			svc.logger.Error("Failed to soft-delete parent collection",
				zap.Any("error", err),
				zap.Any("collection_id", req.ID))
			saga.Rollback(ctx) // Rollback all previous operations
			return nil, err
		}

		// SAGA: Register compensation for parent collection deletion
		// IMPORTANT: Capture by value for closure
		parentCollectionIDCaptured := req.ID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: restoring parent collection",
				zap.String("collection_id", parentCollectionIDCaptured.String()))
			return svc.repo.Restore(ctx, parentCollectionIDCaptured)
		})
	}

	//
	// STEP 10: Update storage tracking (quota, events, daily usage)
	//
	if totalStorageToRelease > 0 {
		svc.logger.Info("Releasing storage quota for collection deletion",
			zap.String("collection_id", req.ID.String()),
			zap.Int64("total_storage_to_release", totalStorageToRelease))

		// Release storage quota
		err = svc.storageQuotaHelperUseCase.OnFileDeleted(ctx, userID, totalStorageToRelease)
		if err != nil {
			svc.logger.Error("Failed to release storage quota after collection deletion",
				zap.Error(err))
			saga.Rollback(ctx)
			return nil, err
		}

		// Register compensation: re-reserve the released quota
		totalStorageCaptured := totalStorageToRelease
		userIDCaptured := userID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: re-reserving released storage quota",
				zap.Int64("size", totalStorageCaptured))
			return svc.storageQuotaHelperUseCase.CheckAndReserveQuota(ctx, userIDCaptured, totalStorageCaptured)
		})

		// Create storage usage event
		err = svc.createStorageUsageEventUseCase.Execute(ctx, userID, totalStorageToRelease, "remove")
		if err != nil {
			svc.logger.Error("Failed to create storage usage event for collection deletion",
				zap.Error(err))
			saga.Rollback(ctx)
			return nil, err
		}

		// Register compensation: create compensating "add" event
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: creating compensating usage event")
			return svc.createStorageUsageEventUseCase.Execute(ctx, userIDCaptured, totalStorageCaptured, "add")
		})

		// Update daily storage usage
		today := time.Now().Truncate(24 * time.Hour)
		updateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
			UserID:      userID,
			UsageDay:    &today,
			TotalBytes:  -totalStorageToRelease,
			AddBytes:    0,
			RemoveBytes: totalStorageToRelease,
			IsIncrement: true,
		}
		err = svc.updateStorageUsageUseCase.Execute(ctx, updateReq)
		if err != nil {
			svc.logger.Error("Failed to update daily storage usage for collection deletion",
				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")
			compensateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
				UserID:      userIDCaptured,
				UsageDay:    &today,
				TotalBytes:  totalStorageCaptured, // Positive to reverse
				AddBytes:    totalStorageCaptured,
				RemoveBytes: 0,
				IsIncrement: true,
			}
			return svc.updateStorageUsageUseCase.Execute(ctx, compensateReq)
		})

		svc.logger.Info("Storage quota released successfully",
			zap.Int64("released_bytes", totalStorageToRelease))
	}

	//
	// STEP 11: Delete all S3 objects
	//
	if len(allStoragePaths) > 0 {
		svc.logger.Info("Deleting S3 objects for collection",
			zap.Any("collection_id", req.ID),
			zap.Int("s3_objects_count", len(allStoragePaths)))

		if err := svc.deleteMultipleDataUseCase.Execute(allStoragePaths); 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.Any("error", err),
				zap.Int("s3_objects_count", len(allStoragePaths)))
		} else {
			svc.logger.Info("Successfully deleted all S3 objects",
				zap.Int("s3_objects_deleted", len(allStoragePaths)))
		}
	}

	svc.logger.Info("Collection hierarchy deleted successfully",
		zap.Any("collection_id", req.ID),
		zap.Int("parent_files_deleted", len(parentFiles)),
		zap.Int("descendant_files_deleted", totalDescendantFiles),
		zap.Int("descendants_deleted", len(descendants)),
		zap.Int("total_files_deleted", len(parentFiles)+totalDescendantFiles),
		zap.Int("s3_objects_deleted", len(allStoragePaths)))

	return &SoftDeleteCollectionResponseDTO{
		Success: true,
		Message: "Collection, descendants, and all associated files deleted successfully",
	}, nil
}
