// monorepo/cloud/backend/internal/maplefile/service/file/complete_file_upload.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"
	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 CompleteFileUploadRequestDTO struct {
	FileID gocql.UUID `json:"file_id"`
	// Optional: Client can provide actual file size for validation
	ActualFileSizeInBytes int64 `json:"actual_file_size_in_bytes,omitempty"`
	// Optional: Client can provide actual thumbnail size for validation
	ActualThumbnailSizeInBytes int64 `json:"actual_thumbnail_size_in_bytes,omitempty"`
	// Optional: Client can confirm successful upload
	UploadConfirmed          bool `json:"upload_confirmed,omitempty"`
	ThumbnailUploadConfirmed bool `json:"thumbnail_upload_confirmed,omitempty"`
}

type CompleteFileUploadResponseDTO struct {
	File                *FileResponseDTO `json:"file"`
	Success             bool             `json:"success"`
	Message             string           `json:"message"`
	ActualFileSize      int64            `json:"actual_file_size"`
	ActualThumbnailSize int64            `json:"actual_thumbnail_size"`
	UploadVerified      bool             `json:"upload_verified"`
	ThumbnailVerified   bool             `json:"thumbnail_verified"`
	StorageAdjustment   int64            `json:"storage_adjustment"` // Positive if more space used, negative if less
}

type CompleteFileUploadService interface {
	Execute(ctx context.Context, req *CompleteFileUploadRequestDTO) (*CompleteFileUploadResponseDTO, error)
}

type completeFileUploadServiceImpl struct {
	config                    *config.Configuration
	logger                    *zap.Logger
	collectionRepo            dom_collection.CollectionRepository
	getMetadataUseCase        uc_filemetadata.GetFileMetadataUseCase
	updateMetadataUseCase     uc_filemetadata.UpdateFileMetadataUseCase
	verifyObjectExistsUseCase uc_fileobjectstorage.VerifyObjectExistsUseCase
	getObjectSizeUseCase      uc_fileobjectstorage.GetObjectSizeUseCase
	deleteDataUseCase         uc_fileobjectstorage.DeleteEncryptedDataUseCase
	storageQuotaHelperUseCase uc_user.UserStorageQuotaHelperUseCase
	// Add storage usage tracking use cases
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase
	updateStorageUsageUseCase      uc_storagedailyusage.UpdateStorageUsageUseCase
}

func NewCompleteFileUploadService(
	config *config.Configuration,
	logger *zap.Logger,
	collectionRepo dom_collection.CollectionRepository,
	getMetadataUseCase uc_filemetadata.GetFileMetadataUseCase,
	updateMetadataUseCase uc_filemetadata.UpdateFileMetadataUseCase,
	verifyObjectExistsUseCase uc_fileobjectstorage.VerifyObjectExistsUseCase,
	getObjectSizeUseCase uc_fileobjectstorage.GetObjectSizeUseCase,
	deleteDataUseCase uc_fileobjectstorage.DeleteEncryptedDataUseCase,
	storageQuotaHelperUseCase uc_user.UserStorageQuotaHelperUseCase,
	createStorageUsageEventUseCase uc_storageusageevent.CreateStorageUsageEventUseCase,
	updateStorageUsageUseCase uc_storagedailyusage.UpdateStorageUsageUseCase,
) CompleteFileUploadService {
	logger = logger.Named("CompleteFileUploadService")
	return &completeFileUploadServiceImpl{
		config:                         config,
		logger:                         logger,
		collectionRepo:                 collectionRepo,
		getMetadataUseCase:             getMetadataUseCase,
		updateMetadataUseCase:          updateMetadataUseCase,
		verifyObjectExistsUseCase:      verifyObjectExistsUseCase,
		getObjectSizeUseCase:           getObjectSizeUseCase,
		deleteDataUseCase:              deleteDataUseCase,
		storageQuotaHelperUseCase:      storageQuotaHelperUseCase,
		createStorageUsageEventUseCase: createStorageUsageEventUseCase,
		updateStorageUsageUseCase:      updateStorageUsageUseCase,
	}
}

func (svc *completeFileUploadServiceImpl) Execute(ctx context.Context, req *CompleteFileUploadRequestDTO) (*CompleteFileUploadResponseDTO, error) {
	//
	// STEP 1: Validation
	//
	if req == nil {
		svc.logger.Warn("⚠️ Failed validation with nil request")
		return nil, httperror.NewForBadRequestWithSingleField("non_field_error", "File completion details are 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
	//
	// Developers note: Use `ExecuteWithAnyState` because initially created `FileMetadata` object has state set to `pending`.
	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))
		return nil, err
	}

	//
	// STEP 4: Verify 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 completion 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 complete this file upload")
	}

	//
	// STEP 5: Verify file is in pending state
	//
	if file.State != dom_file.FileStatePending {
		svc.logger.Warn("⚠️ File is not in pending state",
			zap.Any("file_id", req.FileID),
			zap.String("current_state", file.State))
		return nil, httperror.NewForBadRequestWithSingleField("file_id", fmt.Sprintf("File is not in pending state (current state: %s)", file.State))
	}

	//
	// STEP 6: Verify file exists in object storage and get actual size
	//
	fileExists, err := svc.verifyObjectExistsUseCase.Execute(file.EncryptedFileObjectKey)
	if err != nil {
		svc.logger.Error("🔴 Failed to verify file exists in storage",
			zap.Any("error", err),
			zap.Any("file_id", req.FileID),
			zap.String("storage_path", file.EncryptedFileObjectKey))
		return nil, httperror.NewForInternalServerErrorWithSingleField("message", "Failed to verify file upload")
	}

	if !fileExists {
		svc.logger.Warn("⚠️ File does not exist in storage",
			zap.Any("file_id", req.FileID),
			zap.String("storage_path", file.EncryptedFileObjectKey))
		return nil, httperror.NewForBadRequestWithSingleField("file_id", "File has not been uploaded yet")
	}

	// Get actual file size from storage
	actualFileSize, err := svc.getObjectSizeUseCase.Execute(file.EncryptedFileObjectKey)
	if err != nil {
		svc.logger.Error("🔴 Failed to get file size from storage",
			zap.Any("error", err),
			zap.Any("file_id", req.FileID),
			zap.String("storage_path", file.EncryptedFileObjectKey))
		return nil, httperror.NewForInternalServerErrorWithSingleField("message", "Failed to verify file size")
	}

	//
	// STEP 7: Verify thumbnail if expected
	//
	var actualThumbnailSize int64 = 0
	var thumbnailVerified bool = true

	if file.EncryptedThumbnailObjectKey != "" {
		thumbnailExists, err := svc.verifyObjectExistsUseCase.Execute(file.EncryptedThumbnailObjectKey)
		if err != nil {
			svc.logger.Warn("⚠️ Failed to verify thumbnail exists, continuing without it",
				zap.Any("error", err),
				zap.Any("file_id", req.FileID),
				zap.String("thumbnail_storage_path", file.EncryptedThumbnailObjectKey))
			thumbnailVerified = false
		} else if thumbnailExists {
			actualThumbnailSize, err = svc.getObjectSizeUseCase.Execute(file.EncryptedThumbnailObjectKey)
			if err != nil {
				svc.logger.Warn("⚠️ Failed to get thumbnail size, continuing without it",
					zap.Any("error", err),
					zap.Any("file_id", req.FileID),
					zap.String("thumbnail_storage_path", file.EncryptedThumbnailObjectKey))
				thumbnailVerified = false
			}
		} else {
			// Thumbnail was expected but not uploaded - clear the path
			file.EncryptedThumbnailObjectKey = ""
			thumbnailVerified = false
		}
	}

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

	//
	// STEP 8: Calculate storage adjustment and update quota
	//
	expectedTotalSize := file.EncryptedFileSizeInBytes + file.EncryptedThumbnailSizeInBytes
	actualTotalSize := actualFileSize + actualThumbnailSize
	storageAdjustment := actualTotalSize - expectedTotalSize

	svc.logger.Info("Starting file upload completion with SAGA protection",
		zap.String("file_id", req.FileID.String()),
		zap.Int64("expected_file_size", file.EncryptedFileSizeInBytes),
		zap.Int64("actual_file_size", actualFileSize),
		zap.Int64("expected_thumbnail_size", file.EncryptedThumbnailSizeInBytes),
		zap.Int64("actual_thumbnail_size", actualThumbnailSize),
		zap.Int64("expected_total", expectedTotalSize),
		zap.Int64("actual_total", actualTotalSize),
		zap.Int64("adjustment", storageAdjustment))

	// Handle storage quota adjustment (SAGA protected)
	if storageAdjustment != 0 {
		if storageAdjustment > 0 {
			// Need more quota than originally reserved
			err = svc.storageQuotaHelperUseCase.CheckAndReserveQuota(ctx, userID, storageAdjustment)
			if err != nil {
				svc.logger.Error("Failed to reserve additional storage quota",
					zap.String("user_id", userID.String()),
					zap.Int64("additional_size", storageAdjustment),
					zap.Error(err))

				// Clean up the uploaded file since we can't complete due to quota
				// Note: This is an exceptional case - quota exceeded before any SAGA operations
				if deleteErr := svc.deleteDataUseCase.Execute(file.EncryptedFileObjectKey); deleteErr != nil {
					svc.logger.Error("Failed to clean up file after quota exceeded", zap.Error(deleteErr))
				}
				if file.EncryptedThumbnailObjectKey != "" {
					if deleteErr := svc.deleteDataUseCase.Execute(file.EncryptedThumbnailObjectKey); deleteErr != nil {
						svc.logger.Error("Failed to clean up thumbnail after quota exceeded", zap.Error(deleteErr))
					}
				}

				saga.Rollback(ctx)
				return nil, err
			}

			// Register compensation: release the additional quota if later steps fail
			storageAdjustmentCaptured := storageAdjustment
			userIDCaptured := userID
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: releasing additional reserved quota",
					zap.Int64("size", storageAdjustmentCaptured))
				return svc.storageQuotaHelperUseCase.ReleaseQuota(ctx, userIDCaptured, storageAdjustmentCaptured)
			})
		} else {
			// Used less quota than originally reserved, release the difference
			err = svc.storageQuotaHelperUseCase.ReleaseQuota(ctx, userID, -storageAdjustment)
			if err != nil {
				svc.logger.Error("Failed to release excess quota",
					zap.String("user_id", userID.String()),
					zap.Int64("excess_size", -storageAdjustment),
					zap.Error(err))
				saga.Rollback(ctx)
				return nil, err
			}

			// Register compensation: re-reserve the released quota if later steps fail
			excessQuotaCaptured := -storageAdjustment
			userIDCaptured := userID
			saga.AddCompensation(func(ctx context.Context) error {
				svc.logger.Warn("SAGA compensation: re-reserving released excess quota",
					zap.Int64("size", excessQuotaCaptured))
				return svc.storageQuotaHelperUseCase.CheckAndReserveQuota(ctx, userIDCaptured, excessQuotaCaptured)
			})
		}
	}

	//
	// STEP 9: Validate file size if client provided it
	//
	if req.ActualFileSizeInBytes > 0 && req.ActualFileSizeInBytes != actualFileSize {
		svc.logger.Warn("⚠️ File size mismatch between client and storage",
			zap.Any("file_id", req.FileID),
			zap.Int64("client_reported_size", req.ActualFileSizeInBytes),
			zap.Int64("storage_actual_size", actualFileSize))
		// Continue with storage size as authoritative
	}

	//
	// STEP 10: Update file metadata to active state (SAGA protected)
	//
	originalState := file.State
	originalFileSizeInBytes := file.EncryptedFileSizeInBytes
	originalThumbnailSizeInBytes := file.EncryptedThumbnailSizeInBytes
	originalVersion := file.Version

	file.EncryptedFileSizeInBytes = actualFileSize
	file.EncryptedThumbnailSizeInBytes = actualThumbnailSize
	file.State = dom_file.FileStateActive
	file.ModifiedAt = time.Now()
	file.ModifiedByUserID = userID
	file.Version++ // Every mutation we need to keep a track of.

	err = svc.updateMetadataUseCase.Execute(ctx, file)
	if err != nil {
		svc.logger.Error("Failed to update file metadata to active state",
			zap.Error(err),
			zap.String("file_id", req.FileID.String()))
		saga.Rollback(ctx)
		return nil, err
	}

	// Register compensation: restore original metadata state
	fileIDCaptured := file.ID
	originalStateCaptured := originalState
	originalFileSizeCaptured := originalFileSizeInBytes
	originalThumbnailSizeCaptured := originalThumbnailSizeInBytes
	originalVersionCaptured := originalVersion
	collectionIDCaptured := file.CollectionID
	saga.AddCompensation(func(ctx context.Context) error {
		svc.logger.Warn("SAGA compensation: restoring file metadata to pending state",
			zap.String("file_id", fileIDCaptured.String()))

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

		restoredFile.State = originalStateCaptured
		restoredFile.EncryptedFileSizeInBytes = originalFileSizeCaptured
		restoredFile.EncryptedThumbnailSizeInBytes = originalThumbnailSizeCaptured
		restoredFile.Version = originalVersionCaptured
		restoredFile.ModifiedAt = time.Now()

		// Note: The repository Update method handles file count adjustments based on state changes,
		// so restoring to pending state will automatically decrement the file count
		return svc.updateMetadataUseCase.Execute(ctx, restoredFile)
	})

	// Note: File count increment is handled by the repository's Update method when state changes
	// from pending to active. No explicit increment needed here to avoid double counting.

	//
	// STEP 11: Create storage usage event (SAGA protected)
	//
	_ = collectionIDCaptured // Keep variable for potential future use
	err = svc.createStorageUsageEventUseCase.Execute(ctx, file.OwnerID, actualTotalSize, "add")
	if err != nil {
		svc.logger.Error("Failed to create storage usage event",
			zap.String("owner_id", file.OwnerID.String()),
			zap.Int64("file_size", actualTotalSize),
			zap.Error(err))
		saga.Rollback(ctx)
		return nil, err
	}

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

	//
	// STEP 13: Update daily storage usage (SAGA protected)
	//
	today := time.Now().Truncate(24 * time.Hour)
	updateReq := &uc_storagedailyusage.UpdateStorageUsageRequest{
		UserID:      file.OwnerID,
		UsageDay:    &today,
		TotalBytes:  actualTotalSize,
		AddBytes:    actualTotalSize,
		RemoveBytes: 0,
		IsIncrement: true, // Increment the existing values
	}
	err = svc.updateStorageUsageUseCase.Execute(ctx, updateReq)
	if err != nil {
		svc.logger.Error("Failed to update daily storage usage",
			zap.String("owner_id", file.OwnerID.String()),
			zap.Int64("file_size", actualTotalSize),
			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:      ownerIDCaptured,
			UsageDay:    &today,
			TotalBytes:  -actualTotalSizeCaptured, // Negative to reverse
			AddBytes:    0,
			RemoveBytes: actualTotalSizeCaptured,
			IsIncrement: true,
		}
		return svc.updateStorageUsageUseCase.Execute(ctx, compensateReq)
	})

	//
	// SUCCESS: All operations completed with SAGA protection
	//
	svc.logger.Info("File upload completed successfully with SAGA protection",
		zap.String("file_id", req.FileID.String()),
		zap.String("collection_id", file.CollectionID.String()),
		zap.String("owner_id", file.OwnerID.String()),
		zap.Int64("actual_file_size", actualFileSize),
		zap.Int64("actual_thumbnail_size", actualThumbnailSize),
		zap.Int64("storage_adjustment", storageAdjustment))

	return &CompleteFileUploadResponseDTO{
		File:                mapFileToDTO(file),
		Success:             true,
		Message:             "File upload completed successfully with storage quota updated",
		ActualFileSize:      actualFileSize,
		ActualThumbnailSize: actualThumbnailSize,
		UploadVerified:      true,
		ThumbnailVerified:   thumbnailVerified,
		StorageAdjustment:   storageAdjustment,
	}, nil
}
