// monorepo/cloud/backend/internal/maplefile/service/file/update.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"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/crypto"
	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	uc_filemetadata "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/filemetadata"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)

type UpdateFileRequestDTO struct {
	ID                gocql.UUID            `json:"id"`
	EncryptedMetadata string                `json:"encrypted_metadata,omitempty"`
	EncryptedFileKey  crypto.EncryptedFileKey `json:"encrypted_file_key,omitempty"`
	EncryptionVersion string                `json:"encryption_version,omitempty"`
	EncryptedHash     string                `json:"encrypted_hash,omitempty"`
	Version           uint64                `json:"version,omitempty"`
}

type UpdateFileService interface {
	Execute(ctx context.Context, req *UpdateFileRequestDTO) (*FileResponseDTO, error)
}

type updateFileServiceImpl struct {
	config                *config.Configuration
	logger                *zap.Logger
	collectionRepo        dom_collection.CollectionRepository
	getMetadataUseCase    uc_filemetadata.GetFileMetadataUseCase
	updateMetadataUseCase uc_filemetadata.UpdateFileMetadataUseCase
}

func NewUpdateFileService(
	config *config.Configuration,
	logger *zap.Logger,
	collectionRepo dom_collection.CollectionRepository,
	getMetadataUseCase uc_filemetadata.GetFileMetadataUseCase,
	updateMetadataUseCase uc_filemetadata.UpdateFileMetadataUseCase,
) UpdateFileService {
	logger = logger.Named("UpdateFileService")
	return &updateFileServiceImpl{
		config:                config,
		logger:                logger,
		collectionRepo:        collectionRepo,
		getMetadataUseCase:    getMetadataUseCase,
		updateMetadataUseCase: updateMetadataUseCase,
	}
}

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

	if req.ID.String() == "" {
		svc.logger.Warn("Empty file ID provided")
		return nil, httperror.NewForBadRequestWithSingleField("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 existing file metadata
	//
	file, err := svc.getMetadataUseCase.Execute(req.ID)
	if err != nil {
		svc.logger.Error("Failed to get file metadata",
			zap.Any("error", err),
			zap.Any("file_id", req.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 update attempt",
			zap.Any("user_id", userID),
			zap.Any("file_id", req.ID),
			zap.Any("collection_id", file.CollectionID))
		return nil, httperror.NewForForbiddenWithSingleField("message", "You don't have permission to update this file")
	}

	//
	// STEP 5: Check if submitted collection request is in-sync with our backend's collection copy.
	//

	// Developers note:
	// What is the purpose of this check?
	// Our server has multiple clients sharing data and hence our backend needs to ensure that the file being updated is the most recent version.
	if file.Version != req.Version {
		svc.logger.Warn("Outdated collection update attempt",
			zap.Any("user_id", userID),
			zap.Any("collection_id", req.ID),
			zap.Any("submitted_version", req.Version),
			zap.Any("current_version", file.Version))
		return nil, httperror.NewForBadRequestWithSingleField("message", "Collection has been updated since you last fetched it")
	}

	//
	// STEP 6: Update file metadata
	//
	updated := false

	if req.EncryptedMetadata != "" {
		file.EncryptedMetadata = req.EncryptedMetadata
		updated = true
	}
	if req.EncryptedFileKey.Ciphertext != nil && len(req.EncryptedFileKey.Ciphertext) > 0 {
		file.EncryptedFileKey = req.EncryptedFileKey
		updated = true
	}
	if req.EncryptionVersion != "" {
		file.EncryptionVersion = req.EncryptionVersion
		updated = true
	}
	if req.EncryptedHash != "" {
		file.EncryptedHash = req.EncryptedHash
		updated = true
	}

	if !updated {
		svc.logger.Warn("No fields to update provided")
		return nil, httperror.NewForBadRequestWithSingleField("non_field_error", "At least one field must be provided for update")
	}

	file.Version++ // Mutation means we increment version.
	file.ModifiedAt = time.Now()
	file.ModifiedByUserID = userID

	//
	// STEP 6: Save updated file
	//
	err = svc.updateMetadataUseCase.Execute(ctx, file)
	if err != nil {
		svc.logger.Error("Failed to update file metadata",
			zap.Any("error", err),
			zap.Any("file_id", file.ID))
		return nil, err
	}

	//
	// STEP 7: Map domain model to response DTO
	//
	response := mapFileToDTO(file)

	svc.logger.Debug("File updated successfully",
		zap.Any("file_id", file.ID))

	return response, nil
}
