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

import (
	"context"
	"fmt"
	"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"
	uc_blockedemail "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/blockedemail"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/emailer/mailgun"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/transaction"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
	"github.com/gocql/gocql"
)

type ShareCollectionRequestDTO struct {
	CollectionID           gocql.UUID `json:"collection_id"`
	RecipientID            gocql.UUID `json:"recipient_id"`
	RecipientEmail         string     `json:"recipient_email"`
	PermissionLevel        string     `json:"permission_level"`
	EncryptedCollectionKey []byte     `json:"encrypted_collection_key"`
	ShareWithDescendants   bool       `json:"share_with_descendants"`
}

type ShareCollectionResponseDTO struct {
	Success            bool   `json:"success"`
	Message            string `json:"message"`
	MembershipsCreated int    `json:"memberships_created,omitempty"`
}

type ShareCollectionService interface {
	Execute(ctx context.Context, req *ShareCollectionRequestDTO) (*ShareCollectionResponseDTO, error)
}

type shareCollectionServiceImpl struct {
	config              *config.Configuration
	logger              *zap.Logger
	repo                dom_collection.CollectionRepository
	checkBlockedEmailUC uc_blockedemail.CheckBlockedEmailUseCase
	userGetByIDUC       uc_user.UserGetByIDUseCase
	emailer             mailgun.Emailer
}

func NewShareCollectionService(
	config *config.Configuration,
	logger *zap.Logger,
	repo dom_collection.CollectionRepository,
	checkBlockedEmailUC uc_blockedemail.CheckBlockedEmailUseCase,
	userGetByIDUC uc_user.UserGetByIDUseCase,
	emailer mailgun.Emailer,
) ShareCollectionService {
	logger = logger.Named("ShareCollectionService")
	return &shareCollectionServiceImpl{
		config:              config,
		logger:              logger,
		repo:                repo,
		checkBlockedEmailUC: checkBlockedEmailUC,
		userGetByIDUC:       userGetByIDUC,
		emailer:             emailer,
	}
}

func (svc *shareCollectionServiceImpl) Execute(ctx context.Context, req *ShareCollectionRequestDTO) (*ShareCollectionResponseDTO, error) {
	//
	// STEP 1: Enhanced Validation with Detailed Logging
	//
	if req == nil {
		svc.logger.Warn("Failed validation with nil request")
		return nil, httperror.NewBadRequestError("Share details are required")
	}

	// Log the incoming request for debugging (PII masked for security)
	svc.logger.Debug("received share collection request",
		zap.String("collection_id", req.CollectionID.String()),
		zap.String("recipient_id", req.RecipientID.String()),
		zap.String("recipient_email", validation.MaskEmail(req.RecipientEmail)),
		zap.String("permission_level", req.PermissionLevel),
		zap.Int("encrypted_key_length", len(req.EncryptedCollectionKey)),
		zap.Bool("share_with_descendants", req.ShareWithDescendants))

	e := make(map[string]string)
	if req.CollectionID.String() == "" {
		e["collection_id"] = "Collection ID is required"
	}
	if req.RecipientID.String() == "" {
		e["recipient_id"] = "Recipient ID is required"
	}
	if req.RecipientEmail == "" {
		e["recipient_email"] = "Recipient email is required"
	}
	if req.PermissionLevel == "" {
		// Will default to read-only in repository
	} else if req.PermissionLevel != dom_collection.CollectionPermissionReadOnly &&
		req.PermissionLevel != dom_collection.CollectionPermissionReadWrite &&
		req.PermissionLevel != dom_collection.CollectionPermissionAdmin {
		e["permission_level"] = "Invalid permission level"
	}

	// CRITICAL: Validate encrypted collection key is present and has valid format
	// Note: We use generic error messages to avoid revealing cryptographic implementation details
	const (
		minEncryptedKeySize = 32   // Minimum expected size for encrypted key
		maxEncryptedKeySize = 1024 // Maximum reasonable size to prevent abuse
	)

	if len(req.EncryptedCollectionKey) == 0 {
		svc.logger.Error("encrypted collection key validation failed",
			zap.String("collection_id", req.CollectionID.String()),
			zap.String("recipient_id", req.RecipientID.String()),
			zap.Int("encrypted_key_length", len(req.EncryptedCollectionKey)))
		e["encrypted_collection_key"] = "Encrypted collection key is required"
	} else if len(req.EncryptedCollectionKey) < minEncryptedKeySize || len(req.EncryptedCollectionKey) > maxEncryptedKeySize {
		// Generic error message - don't reveal size expectations
		svc.logger.Error("encrypted collection key has invalid size",
			zap.String("collection_id", req.CollectionID.String()),
			zap.String("recipient_id", req.RecipientID.String()),
			zap.Int("encrypted_key_length", len(req.EncryptedCollectionKey)))
		e["encrypted_collection_key"] = "Encrypted collection key is invalid"
	}

	if len(e) != 0 {
		svc.logger.Warn("Failed validation",
			zap.Any("error", e))
		return nil, httperror.NewValidationError(e)
	}

	//
	// 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.NewInternalServerError("Authentication context error")
	}

	//
	// STEP 3: Retrieve existing collection
	//
	collection, err := svc.repo.Get(ctx, req.CollectionID)
	if err != nil {
		svc.logger.Error("Failed to get collection",
			zap.Any("error", err),
			zap.Any("collection_id", req.CollectionID))
		return nil, err
	}

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

	//
	// STEP 4: Check if user has rights to share this collection
	//
	hasSharePermission := false

	// Owner always has share permission
	if collection.OwnerID == userID {
		hasSharePermission = true
	} else {
		// Check if user is an admin member
		for _, member := range collection.Members {
			if member.RecipientID == userID && member.PermissionLevel == dom_collection.CollectionPermissionAdmin {
				hasSharePermission = true
				break
			}
		}
	}

	if !hasSharePermission {
		svc.logger.Warn("Unauthorized collection sharing attempt",
			zap.Any("user_id", userID),
			zap.Any("collection_id", req.CollectionID))
		return nil, httperror.NewForbiddenError("You don't have permission to share this collection")
	}

	//
	// STEP 5: Validate that we're not sharing with the owner (redundant)
	//
	if req.RecipientID == collection.OwnerID {
		svc.logger.Warn("Attempt to share collection with its owner",
			zap.String("collection_id", req.CollectionID.String()),
			zap.String("owner_id", collection.OwnerID.String()),
			zap.String("recipient_id", req.RecipientID.String()))
		return nil, httperror.NewValidationError(map[string]string{"recipient_id": "Cannot share collection with its owner"})
	}

	//
	// STEP 5.5: Check if the recipient has blocked the sender
	//
	// Get the sender's email by looking up the user
	sender, err := svc.userGetByIDUC.Execute(ctx, userID)
	if err != nil {
		svc.logger.Error("Failed to get sender user info",
			zap.Any("error", err),
			zap.String("user_id", userID.String()))
		// Don't block the sharing if we can't get user info - continue without check
	} else if sender != nil && sender.Email != "" {
		isBlocked, err := svc.checkBlockedEmailUC.Execute(ctx, req.RecipientID, sender.Email)
		if err != nil {
			svc.logger.Error("Failed to check blocked email status",
				zap.Any("error", err),
				zap.String("recipient_id", req.RecipientID.String()),
				zap.String("sender_email", validation.MaskEmail(sender.Email)))
			// Don't block the sharing if we can't check - log and continue
		} else if isBlocked {
			svc.logger.Info("Sharing blocked by recipient",
				zap.String("collection_id", req.CollectionID.String()),
				zap.String("recipient_id", req.RecipientID.String()),
				zap.String("sender_email", validation.MaskEmail(sender.Email)))
			return nil, httperror.NewForbiddenError("Unable to share with this user. You may have been blocked.")
		}
	}

	//
	// STEP 6: Create membership with EXPLICIT validation
	//
	svc.logger.Info("creating membership with validated encrypted key",
		zap.String("collection_id", req.CollectionID.String()),
		zap.String("recipient_id", req.RecipientID.String()),
		zap.Int("encrypted_key_length", len(req.EncryptedCollectionKey)),
		zap.String("permission_level", req.PermissionLevel))

	membership := &dom_collection.CollectionMembership{
		ID:                     gocql.TimeUUID(),
		CollectionID:           req.CollectionID,
		RecipientID:            req.RecipientID,
		RecipientEmail:         req.RecipientEmail,
		GrantedByID:            userID,
		EncryptedCollectionKey: req.EncryptedCollectionKey, // This should NEVER be nil for shared members
		PermissionLevel:        req.PermissionLevel,
		CreatedAt:              time.Now(),
		IsInherited:            false,
	}

	// DOUBLE-CHECK: Verify the membership has the encrypted key before proceeding
	if len(membership.EncryptedCollectionKey) == 0 {
		svc.logger.Error("CRITICAL: Membership created without encrypted collection key",
			zap.String("collection_id", req.CollectionID.String()),
			zap.String("recipient_id", req.RecipientID.String()),
			zap.String("membership_id", membership.ID.String()))
		return nil, httperror.NewInternalServerError("Failed to create membership with encrypted key")
	}

	svc.logger.Info("membership created successfully with encrypted key",
		zap.String("collection_id", req.CollectionID.String()),
		zap.String("recipient_id", req.RecipientID.String()),
		zap.String("membership_id", membership.ID.String()),
		zap.Int("encrypted_key_length", len(membership.EncryptedCollectionKey)))

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

	//
	// STEP 7: Add membership to collection
	//
	var membershipsCreated int = 1

	if req.ShareWithDescendants {
		// Add member to collection and all descendants
		err = svc.repo.AddMemberToHierarchy(ctx, req.CollectionID, membership)
		if err != nil {
			svc.logger.Error("Failed to add member to collection hierarchy",
				zap.Any("error", err),
				zap.Any("collection_id", req.CollectionID),
				zap.Any("recipient_id", req.RecipientID))
			saga.Rollback(ctx) // Rollback any previous operations
			return nil, err
		}

		// SAGA: Register compensation for hierarchical membership addition
		// IMPORTANT: Capture by value for closure
		collectionIDCaptured := req.CollectionID
		recipientIDCaptured := req.RecipientID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: removing member from collection hierarchy",
				zap.String("collection_id", collectionIDCaptured.String()),
				zap.String("recipient_id", recipientIDCaptured.String()))
			return svc.repo.RemoveMemberFromHierarchy(ctx, collectionIDCaptured, recipientIDCaptured)
		})

		// Get the number of descendants to report how many memberships were created
		descendants, err := svc.repo.FindDescendants(ctx, req.CollectionID)
		if err == nil {
			membershipsCreated += len(descendants)
		}
	} else {
		// Add member just to this collection
		err = svc.repo.AddMember(ctx, req.CollectionID, membership)
		if err != nil {
			svc.logger.Error("Failed to add member to collection",
				zap.Any("error", err),
				zap.Any("collection_id", req.CollectionID),
				zap.Any("recipient_id", req.RecipientID))
			saga.Rollback(ctx) // Rollback any previous operations
			return nil, err
		}

		// SAGA: Register compensation for single membership addition
		// IMPORTANT: Capture by value for closure
		collectionIDCaptured := req.CollectionID
		recipientIDCaptured := req.RecipientID
		saga.AddCompensation(func(ctx context.Context) error {
			svc.logger.Warn("SAGA compensation: removing member from collection",
				zap.String("collection_id", collectionIDCaptured.String()),
				zap.String("recipient_id", recipientIDCaptured.String()))
			return svc.repo.RemoveMember(ctx, collectionIDCaptured, recipientIDCaptured)
		})
	}

	svc.logger.Info("Collection shared successfully",
		zap.Any("collection_id", req.CollectionID),
		zap.Any("recipient_id", req.RecipientID),
		zap.Any("granted_by", userID),
		zap.String("permission_level", req.PermissionLevel),
		zap.Bool("shared_with_descendants", req.ShareWithDescendants),
		zap.Int("memberships_created", membershipsCreated))

	//
	// STEP 8: Send email notification to recipient (best effort)
	//
	go svc.sendShareNotificationEmail(ctx, req.RecipientID, req.RecipientEmail)

	return &ShareCollectionResponseDTO{
		Success:            true,
		Message:            "Collection shared successfully",
		MembershipsCreated: membershipsCreated,
	}, nil
}

// sendShareNotificationEmail sends a notification email to the recipient about a shared collection.
// This is a best-effort operation - failures are logged but don't affect the share operation.
// Note: This function creates its own background context since it runs in a goroutine after the
// HTTP request context may be canceled.
func (svc *shareCollectionServiceImpl) sendShareNotificationEmail(_ context.Context, recipientID gocql.UUID, recipientEmail string) {
	// Create a new background context with timeout for the async email operation
	// We don't use the request context because it gets canceled when the response is sent
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Get recipient user to check notification preferences
	recipient, err := svc.userGetByIDUC.Execute(ctx, recipientID)
	if err != nil {
		svc.logger.Warn("Failed to get recipient for email notification",
			zap.Error(err),
			zap.String("recipient_id", recipientID.String()))
		return
	}

	if recipient == nil {
		svc.logger.Warn("Recipient not found for email notification",
			zap.String("recipient_id", recipientID.String()))
		return
	}

	// Check if recipient has disabled share notifications
	// Default to true (enabled) if not set
	if recipient.ProfileData != nil &&
		recipient.ProfileData.ShareNotificationsEnabled != nil &&
		!*recipient.ProfileData.ShareNotificationsEnabled {
		svc.logger.Debug("Recipient has disabled share notifications",
			zap.String("recipient_id", recipientID.String()),
			zap.String("recipient_email", validation.MaskEmail(recipientEmail)))
		return
	}

	// Build email content
	subject := "You have a new shared collection on MapleFile"
	sender := svc.emailer.GetSenderEmail()
	frontendURL := svc.emailer.GetFrontendDomainName()

	htmlContent := fmt.Sprintf(`
		<html>
		<body>
			<h2>Hello,</h2>
			<p>Someone has shared a collection with you on MapleFile.</p>
			<p><a href="https://%s" style="color: #4CAF50;">Log in to view it</a></p>
			<br>
			<p style="font-size: 12px; color: #666;">
				You can disable these notifications in your profile settings.
			</p>
		</body>
		</html>
	`, frontendURL)

	// Send the email
	if err := svc.emailer.Send(ctx, sender, subject, recipientEmail, htmlContent); err != nil {
		svc.logger.Warn("Failed to send share notification email",
			zap.Error(err),
			zap.String("recipient_email", validation.MaskEmail(recipientEmail)))
		return
	}

	svc.logger.Debug("Share notification email sent",
		zap.String("recipient_email", validation.MaskEmail(recipientEmail)))
}
