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

import (
	"context"

	"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"
	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/ratelimit"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

type GetCollectionService interface {
	Execute(ctx context.Context, collectionID gocql.UUID) (*CollectionResponseDTO, error)
}

type getCollectionServiceImpl struct {
	config               *config.Configuration
	logger               *zap.Logger
	repo                 dom_collection.CollectionRepository
	userGetByIDUseCase   uc_user.UserGetByIDUseCase
	authFailureRateLimiter ratelimit.AuthFailureRateLimiter
}

func NewGetCollectionService(
	config *config.Configuration,
	logger *zap.Logger,
	repo dom_collection.CollectionRepository,
	userGetByIDUseCase uc_user.UserGetByIDUseCase,
	authFailureRateLimiter ratelimit.AuthFailureRateLimiter,
) GetCollectionService {
	logger = logger.Named("GetCollectionService")
	return &getCollectionServiceImpl{
		config:               config,
		logger:               logger,
		repo:                 repo,
		userGetByIDUseCase:   userGetByIDUseCase,
		authFailureRateLimiter: authFailureRateLimiter,
	}
}

func (svc *getCollectionServiceImpl) Execute(ctx context.Context, collectionID gocql.UUID) (*CollectionResponseDTO, error) {
	//
	// STEP 1: Validation
	//
	if collectionID.String() == "" {
		svc.logger.Warn("Empty collection ID provided")
		return nil, httperror.NewForBadRequestWithSingleField("collection_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: Get collection from repository
	//
	collection, err := svc.repo.Get(ctx, collectionID)
	if err != nil {
		svc.logger.Error("Failed to get collection",
			zap.Any("error", err),
			zap.Any("collection_id", collectionID))
		return nil, err
	}

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

	//
	// STEP 4: Check rate limiting for authorization failures
	//
	// Check if user has exceeded authorization failure limits before checking access
	// This helps prevent privilege escalation attempts
	if svc.authFailureRateLimiter != nil {
		allowed, remainingAttempts, resetTime, err := svc.authFailureRateLimiter.CheckAuthFailure(
			ctx,
			userID.String(),
			collectionID.String(),
			"collection:get")

		if err != nil {
			// Log error but continue - fail open for availability
			svc.logger.Error("Failed to check auth failure rate limit",
				zap.Error(err),
				zap.Any("user_id", userID),
				zap.Any("collection_id", collectionID))
		} else if !allowed {
			svc.logger.Warn("User blocked due to excessive authorization failures",
				zap.Any("user_id", userID),
				zap.Any("collection_id", collectionID),
				zap.Int("remaining_attempts", remainingAttempts),
				zap.Time("reset_time", resetTime))
			return nil, httperror.NewTooManyRequestsError(
				"Too many authorization failures. Please try again later")
		}
	}

	//
	// STEP 5: Check if the user has access to this collection
	//
	// Use CheckAccess to verify both access and permission level
	// For GET operations, read_only permission is sufficient
	hasAccess, err := svc.repo.CheckAccess(ctx, collectionID, userID, dom_collection.CollectionPermissionReadOnly)
	if err != nil {
		svc.logger.Error("Failed to check collection access",
			zap.Error(err),
			zap.Any("user_id", userID),
			zap.Any("collection_id", collectionID))
		return nil, httperror.NewInternalServerError("Failed to check collection access")
	}

	if !hasAccess {
		// Record authorization failure for rate limiting
		if svc.authFailureRateLimiter != nil {
			if err := svc.authFailureRateLimiter.RecordAuthFailure(
				ctx,
				userID.String(),
				collectionID.String(),
				"collection:get",
				"insufficient_permission"); err != nil {
				svc.logger.Error("Failed to record auth failure",
					zap.Error(err),
					zap.Any("user_id", userID),
					zap.Any("collection_id", collectionID))
			}
		}

		svc.logger.Warn("Unauthorized collection access attempt",
			zap.Any("user_id", userID),
			zap.Any("collection_id", collectionID),
			zap.String("required_permission", dom_collection.CollectionPermissionReadOnly))
		return nil, httperror.NewForForbiddenWithSingleField("message", "You don't have access to this collection")
	}

	// Record successful authorization
	if svc.authFailureRateLimiter != nil {
		if err := svc.authFailureRateLimiter.RecordAuthSuccess(
			ctx,
			userID.String(),
			collectionID.String(),
			"collection:get"); err != nil {
			svc.logger.Debug("Failed to record auth success",
				zap.Error(err),
				zap.Any("user_id", userID),
				zap.Any("collection_id", collectionID))
		}
	}

	//
	// STEP 5: Get owner's email
	//
	var ownerEmail string
	svc.logger.Info("🔍 GetCollectionService: Looking up owner email",
		zap.String("collection_id", collectionID.String()),
		zap.String("owner_id", collection.OwnerID.String()))

	owner, err := svc.userGetByIDUseCase.Execute(ctx, collection.OwnerID)
	if err != nil {
		svc.logger.Warn("Failed to get owner email, continuing without it",
			zap.Any("error", err),
			zap.Any("owner_id", collection.OwnerID))
		// Don't fail the request, just continue without the owner email
	} else if owner != nil {
		ownerEmail = owner.Email
		svc.logger.Info("🔍 GetCollectionService: Found owner email",
			zap.String("owner_email", validation.MaskEmail(ownerEmail)))
	} else {
		svc.logger.Warn("🔍 GetCollectionService: Owner user not found",
			zap.String("owner_id", collection.OwnerID.String()))
	}

	//
	// STEP 6: Map domain model to response DTO
	//
	// Note: We pass collection.FileCount (not 0) to include the actual file count
	// in the response. This field is maintained by IncrementFileCount/DecrementFileCount
	// calls when files are added/removed from the collection.
	//
	svc.logger.Info("🔍 GetCollectionService: Mapping to DTO with owner_email",
		zap.String("owner_email", validation.MaskEmail(ownerEmail)))
	response := mapCollectionToDTO(collection, int(collection.FileCount), ownerEmail)

	return response, nil
}
