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

import (
	"context"
	"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"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	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 SoftDeleteFileRequestDTO struct {
	FileID          gocql.UUID `json:"file_id"`
	ForceHardDelete bool       `json:"force_hard_delete"` // Skip tombstone for GDPR right-to-be-forgotten
}

type SoftDeleteFileResponseDTO struct {
	Success       bool   `json:"success"`
	Message       string `json:"message"`
	ReleasedBytes int64  `json:"released_bytes"` // Amount of storage quota released
}

type SoftDeleteFileService interface {
	Execute(ctx context.Context, req *SoftDeleteFileRequestDTO) (*SoftDeleteFileResponseDTO, error)
}

type softDeleteFileServiceImpl struct {
	config                    *config.Configuration
	logger                    *zap.Logger
	collectionRepo            dom_collection.CollectionRepository
	getMetadataUseCase        uc_filemetadata.GetFileMetadataUseCase
	updateFileMetadataUseCase uc_filemetadata.UpdateFileMetadataUseCase
	softDeleteMetadataUseCase uc_filemetadata.SoftDeleteFileMetadataUseCase
	hardDeleteMetadataUseCase uc_filemetadata.HardDeleteFileMetadataUseCase
	deleteDataUseCase         uc_fileobjectstorage.DeleteEncryptedDataUseCase
	listFilesByOwnerIDService ListFilesByOwnerIDService
	// Storage quota management
	storageQuotaHelperUseCase uc_user.UserStorageQuotaHelperUseCase
	// Add storage usage tracking use cases
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase
	updateStorageUsageUseCase      uc_storagedailyusage.UpdateStorageUsageUseCase
}

func NewSoftDeleteFileService(
	config *config.Configuration,
	logger *zap.Logger,
	collectionRepo dom_collection.CollectionRepository,
	getMetadataUseCase uc_filemetadata.GetFileMetadataUseCase,
	updateFileMetadataUseCase uc_filemetadata.UpdateFileMetadataUseCase,
	softDeleteMetadataUseCase uc_filemetadata.SoftDeleteFileMetadataUseCase,
	hardDeleteMetadataUseCase uc_filemetadata.HardDeleteFileMetadataUseCase,
	deleteDataUseCase uc_fileobjectstorage.DeleteEncryptedDataUseCase,
	listFilesByOwnerIDService ListFilesByOwnerIDService,
	storageQuotaHelperUseCase uc_user.UserStorageQuotaHelperUseCase,
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase,
	updateStorageUsageUseCase uc_storagedailyusage.UpdateStorageUsageUseCase,
) SoftDeleteFileService {
	logger = logger.Named("SoftDeleteFileService")
	return &softDeleteFileServiceImpl{
		config:                         config,
		logger:                         logger,
		collectionRepo:                 collectionRepo,
		getMetadataUseCase:             getMetadataUseCase,
		updateFileMetadataUseCase:      updateFileMetadataUseCase,
		softDeleteMetadataUseCase:      softDeleteMetadataUseCase,
		hardDeleteMetadataUseCase:      hardDeleteMetadataUseCase,
		deleteDataUseCase:              deleteDataUseCase,
		listFilesByOwnerIDService:      listFilesByOwnerIDService,
		storageQuotaHelperUseCase:      storageQuotaHelperUseCase,
		createStorageUsageEventUseCase: createStorageUsageEventUseCase,
		updateStorageUsageUseCase:      updateStorageUsageUseCase,
	}
}

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

	if req.FileID.String() == "" {
		svc.logger.Warn("Empty file ID provided")
		return nil, httperror.NewForBadRequestWithSingleField("file_id", "File 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: Get file metadata
	//
	file, err := svc.getMetadataUseCase.Execute(req.FileID)
	if err != nil {
		svc.logger.Error("Failed to get file metadata",
			zap.Any("error", err),
			zap.Any("file_id", req.FileID))

		svc.logger.Debug("Debugging started, will list all files that belong to the authenticated user")
		currentFiles, err := svc.listFilesByOwnerIDService.Execute(ctx, &ListFilesByOwnerIDRequestDTO{OwnerID: userID})
		if err != nil {
			svc.logger.Error("Failed to list files by owner ID",
				zap.Any("error", err),
				zap.Any("user_id", userID))
			return nil, err
		}
		for _, file := range currentFiles.Files {
			svc.logger.Debug("File",
				zap.Any("id", file.ID))
		}

		return nil, err
	}

	//
	// STEP 4: Check if user has write access to the file's collection
	//
	hasAccess, err := svc.collectionRepo.CheckAccess(ctx, file.CollectionID, userID, dom_collection.CollectionPermissionReadWrite)
	if err != nil {
		svc.logger.Error("Failed to check collection access",
			zap.Any("error", err),
			zap.Any("collection_id", file.CollectionID),
			zap.Any("user_id", userID))
		return nil, err
	}

	if !hasAccess {
		svc.logger.Warn("Unauthorized file deletion attempt",
			zap.Any("user_id", userID),
			zap.Any("file_id", req.FileID),
			zap.Any("collection_id", file.CollectionID))
		return nil, httperror.NewForForbiddenWithSingleField("message", "You don't have permission to delete this file")
	}

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

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

	//
	// STEP 5: Calculate storage space to be released
	//
	totalFileSize := file.EncryptedFileSizeInBytes + file.EncryptedThumbnailSizeInBytes

	svc.logger.Info("Starting file soft-delete with SAGA protection",
		zap.String("file_id", req.FileID.String()),
		zap.Int64("file_size", file.EncryptedFileSizeInBytes),
		zap.Int64("thumbnail_size", file.EncryptedThumbnailSizeInBytes),
		zap.Int64("total_size_to_release", totalFileSize))

	//
	// STEP 6: Update file metadata with tombstone (SAGA protected)
	//
	originalState := file.State
	originalTombstoneVersion := file.TombstoneVersion
	originalTombstoneExpiry := file.TombstoneExpiry

	file.State = dom_file.FileStateDeleted
	file.Version++
	file.ModifiedAt = time.Now()
	file.ModifiedByUserID = userID
	file.TombstoneVersion = file.Version
	file.TombstoneExpiry = time.Now().Add(30 * 24 * time.Hour)

	if err := svc.updateFileMetadataUseCase.Execute(ctx, file); err != nil {
		svc.logger.Error("Failed to update file metadata with tombstone",
			zap.Error(err))
		saga.Rollback(ctx)
		return nil, err
	}

	// Register compensation: restore original metadata
	fileIDCaptured := file.ID
	originalStateCaptured := originalState
	originalTombstoneVersionCaptured := originalTombstoneVersion
	originalTombstoneExpiryCaptured := originalTombstoneExpiry
	saga.AddCompensation(func(ctx context.Context) error {
		svc.logger.Warn("SAGA compensation: restoring file metadata",
			zap.String("file_id", fileIDCaptured.String()))

		restoredFile, err := svc.getMetadataUseCase.Execute(fileIDCaptured)
		if err != nil {
			return err
		}

		restoredFile.State = originalStateCaptured
		restoredFile.TombstoneVersion = originalTombstoneVersionCaptured
		restoredFile.TombstoneExpiry = originalTombstoneExpiryCaptured
		restoredFile.ModifiedAt = time.Now()

		return svc.updateFileMetadataUseCase.Execute(ctx, restoredFile)
	})

	//
	// STEP 7: Delete file metadata record (SAGA protected)
	//
	if req.ForceHardDelete {
		// Hard delete - permanent removal for GDPR right-to-be-forgotten
		svc.logger.Info("Performing hard delete (GDPR mode) - no tombstone",
			zap.String("file_id", req.FileID.String()))

		err = svc.hardDeleteMetadataUseCase.Execute(req.FileID)
		if err != nil {
			svc.logger.Error("Failed to hard-delete file metadata",
				zap.Error(err))
			saga.Rollback(ctx) // Restores tombstone metadata
			return nil, err
		}

		// No compensation for hard delete - GDPR compliance requires permanent deletion
	} else {
		// Soft delete - 30-day tombstone (standard deletion)
		err = svc.softDeleteMetadataUseCase.Execute(req.FileID)
		if err != nil {
			svc.logger.Error("Failed to soft-delete file metadata",
				zap.Error(err))
			saga.Rollback(ctx) // Restores tombstone metadata
			return nil, err
		}

		// Register compensation: restore metadata record to active state
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: restoring file metadata record to active state",
				zap.String("file_id", fileIDCaptured.String()))

			// Get the soft-deleted file
			deletedFile, err := svc.getMetadataUseCase.Execute(fileIDCaptured)
			if err != nil {
				return err
			}

			// Restore to active state
			deletedFile.State = dom_file.FileStateActive
			deletedFile.ModifiedAt = time.Now()
			deletedFile.Version++
			deletedFile.TombstoneVersion = 0
			deletedFile.TombstoneExpiry = time.Time{}

			return svc.updateFileMetadataUseCase.Execute(ctx, deletedFile)
		})
	}

	//
	// STEP 8: Update collection file count (SAGA protected)
	//
	if originalState == dom_file.FileStateActive {
		err = svc.collectionRepo.DecrementFileCount(ctx, file.CollectionID)
		if err != nil {
			svc.logger.Error("Failed to decrement file count for collection",
				zap.String("collection_id", file.CollectionID.String()),
				zap.Error(err))
			saga.Rollback(ctx)
			return nil, err
		}

		// Register compensation: increment the count back
		collectionIDCaptured := file.CollectionID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: restoring file count",
				zap.String("collection_id", collectionIDCaptured.String()))
			return svc.collectionRepo.IncrementFileCount(ctx, collectionIDCaptured)
		})
	}

	//
	// STEP 9: Release storage quota (SAGA protected)
	//
	var releasedBytes int64 = 0
	if originalState == dom_file.FileStateActive && totalFileSize > 0 {
		err = svc.storageQuotaHelperUseCase.OnFileDeleted(ctx, userID, totalFileSize)
		if err != nil {
			svc.logger.Error("Failed to release storage quota after file deletion",
				zap.Error(err))
			saga.Rollback(ctx) // Restores metadata + tombstone
			return nil, err
		}

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

		releasedBytes = totalFileSize
		svc.logger.Info("Storage quota released successfully",
			zap.Int64("released_bytes", releasedBytes))

		//
		// STEP 10: Create storage usage event (SAGA protected)
		//
		err = svc.createStorageUsageEventUseCase.Execute(ctx, file.OwnerID, totalFileSize, "remove")
		if err != nil {
			svc.logger.Error("Failed to create storage usage event for deletion",
				zap.Error(err))
			saga.Rollback(ctx) // Restores quota + metadata
			return nil, err
		}

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

		//
		// STEP 11: Update daily storage usage (SAGA protected)
		//
		today := time.Now().Truncate(24 * time.Hour)
		updateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
			UserID:      file.OwnerID,
			UsageDay:    &today,
			TotalBytes:  -totalFileSize,
			AddBytes:    0,
			RemoveBytes: totalFileSize,
			IsIncrement: true,
		}
		err = svc.updateStorageUsageUseCase.Execute(ctx, updateReq)
		if err != nil {
			svc.logger.Error("Failed to update daily storage usage for deletion",
				zap.Error(err))
			saga.Rollback(ctx) // Restores everything
			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:      ownerIDCaptured,
				UsageDay:    &today,
				TotalBytes:  totalFileSizeCaptured, // Positive to reverse
				AddBytes:    totalFileSizeCaptured,
				RemoveBytes: 0,
				IsIncrement: true,
			}
			return svc.updateStorageUsageUseCase.Execute(ctx, compensateReq)
		})
	} else if originalState == dom_file.FileStatePending {
		// For pending files, release the reserved quota (SAGA protected)
		err = svc.storageQuotaHelperUseCase.ReleaseQuota(ctx, userID, totalFileSize)
		if err != nil {
			svc.logger.Error("Failed to release reserved storage quota for pending file",
				zap.Error(err))
			saga.Rollback(ctx) // Restores metadata + tombstone
			return nil, err
		}

		// Register compensation: re-reserve the released quota
		totalFileSizeCaptured := totalFileSize
		userIDCaptured := userID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: re-reserving pending file quota")
			return svc.storageQuotaHelperUseCase.CheckAndReserveQuota(ctx, userIDCaptured, totalFileSizeCaptured)
		})

		releasedBytes = totalFileSize
		svc.logger.Info("Reserved storage quota released for pending file",
			zap.Int64("released_bytes", releasedBytes))
	}

	//
	// STEP 12: Delete S3 objects
	//
	var storagePaths []string
	storagePaths = append(storagePaths, file.EncryptedFileObjectKey)
	if file.EncryptedThumbnailObjectKey != "" {
		storagePaths = append(storagePaths, file.EncryptedThumbnailObjectKey)
	}

	svc.logger.Info("Deleting S3 objects for file",
		zap.String("file_id", req.FileID.String()),
		zap.Int("s3_objects_count", len(storagePaths)))

	for _, storagePath := range storagePaths {
		if err := svc.deleteDataUseCase.Execute(storagePath); err != nil {
			// Log but don't fail - S3 deletion is best effort after metadata is deleted
			svc.logger.Error("Failed to delete S3 object (continuing anyway)",
				zap.String("storage_path", storagePath),
				zap.Error(err))
		}
	}

	//
	// SUCCESS: All operations completed with SAGA protection
	//
	svc.logger.Info("File deleted successfully with SAGA protection",
		zap.String("file_id", req.FileID.String()),
		zap.String("collection_id", file.CollectionID.String()),
		zap.Int64("released_bytes", releasedBytes),
		zap.Int("s3_objects_deleted", len(storagePaths)))

	return &SoftDeleteFileResponseDTO{
		Success:       true,
		Message:       "File deleted successfully",
		ReleasedBytes: releasedBytes,
	}, nil
}
