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

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"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/crypto"
	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/tag"
	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/validation"
)

// CreateCollectionRequestDTO represents a Data Transfer Object (DTO)
// used for transferring collection (folder or album) data between the local device and the cloud server.
// This data is end-to-end encrypted (E2EE) on the local device before transmission.
// The cloud server stores this encrypted data but cannot decrypt it.
// On the local device, this data is decrypted for use and storage (not stored in this encrypted DTO format locally).
// It can represent both root collections and embedded subcollections.
type CreateCollectionRequestDTO struct {
	ID                     gocql.UUID                     `bson:"_id" json:"id"`
	OwnerID                gocql.UUID                     `bson:"owner_id" json:"owner_id"`
	EncryptedName          string                         `bson:"encrypted_name" json:"encrypted_name"`
	EncryptedCustomIcon    string                         `bson:"encrypted_custom_icon" json:"encrypted_custom_icon"`
	CollectionType         string                         `bson:"collection_type" json:"collection_type"`
	EncryptedCollectionKey *crypto.EncryptedCollectionKey `bson:"encrypted_collection_key" json:"encrypted_collection_key"`
	Members                []*CollectionMembershipDTO     `bson:"members" json:"members"`
	ParentID               gocql.UUID                     `bson:"parent_id,omitempty" json:"parent_id,omitempty"`
	AncestorIDs            []gocql.UUID                   `bson:"ancestor_ids,omitempty" json:"ancestor_ids,omitempty"`
	TagIDs                 []gocql.UUID                   `bson:"tag_ids,omitempty" json:"tag_ids,omitempty"` // Tag IDs to embed in collection
	Children               []*CreateCollectionRequestDTO  `bson:"children,omitempty" json:"children,omitempty"`
	CreatedAt              time.Time                      `bson:"created_at" json:"created_at"`
	CreatedByUserID        gocql.UUID                     `json:"created_by_user_id"`
	ModifiedAt             time.Time                      `bson:"modified_at" json:"modified_at"`
	ModifiedByUserID       gocql.UUID                     `json:"modified_by_user_id"`
}

type CollectionMembershipDTO struct {
	ID                     gocql.UUID `bson:"_id" json:"id"`
	CollectionID           gocql.UUID `bson:"collection_id" json:"collection_id"`
	RecipientID            gocql.UUID `bson:"recipient_id" json:"recipient_id"`
	RecipientEmail         string     `bson:"recipient_email" json:"recipient_email"`
	GrantedByID            gocql.UUID `bson:"granted_by_id" json:"granted_by_id"`
	EncryptedCollectionKey []byte     `bson:"encrypted_collection_key" json:"encrypted_collection_key"`
	PermissionLevel        string     `bson:"permission_level" json:"permission_level"`
	CreatedAt              time.Time  `bson:"created_at" json:"created_at"`
	IsInherited            bool       `bson:"is_inherited" json:"is_inherited"`
	InheritedFromID        gocql.UUID `bson:"inherited_from_id,omitempty" json:"inherited_from_id,omitempty"`
}

type CollectionResponseDTO struct {
	ID                     gocql.UUID                     `json:"id"`
	OwnerID                gocql.UUID                     `json:"owner_id"`
	OwnerEmail             string                         `json:"owner_email"`
	EncryptedName          string                         `json:"encrypted_name"`
	EncryptedCustomIcon    string                         `json:"encrypted_custom_icon,omitempty"`
	CollectionType         string                         `json:"collection_type"`
	ParentID               gocql.UUID                     `json:"parent_id,omitempty"`
	AncestorIDs            []gocql.UUID                   `json:"ancestor_ids,omitempty"`
	Tags                   []tag.EmbeddedTag              `json:"tags,omitempty"`
	EncryptedCollectionKey *crypto.EncryptedCollectionKey `json:"encrypted_collection_key,omitempty"`
	Children               []*CollectionResponseDTO       `json:"children,omitempty"`
	CreatedAt              time.Time                      `json:"created_at"`
	ModifiedAt             time.Time                      `json:"modified_at"`
	Members                []MembershipResponseDTO        `json:"members"`
	FileCount              int                            `json:"file_count"`
	Version                uint64                         `json:"version"`
}

type MembershipResponseDTO struct {
	ID             gocql.UUID `bson:"_id" json:"id"`
	CollectionID   gocql.UUID `bson:"collection_id" json:"collection_id"`     // ID of the collection (redundant but helpful for queries)
	RecipientID    gocql.UUID `bson:"recipient_id" json:"recipient_id"`       // User receiving access
	RecipientEmail string     `bson:"recipient_email" json:"recipient_email"` // Email for display purposes
	GrantedByID    gocql.UUID `bson:"granted_by_id" json:"granted_by_id"`     // User who shared the collection

	// Collection key encrypted with recipient's public key using box_seal. This matches the box_seal format which doesn't need a separate nonce.
	EncryptedCollectionKey []byte `bson:"encrypted_collection_key" json:"encrypted_collection_key"`

	// Access details
	PermissionLevel string    `bson:"permission_level" json:"permission_level"`
	CreatedAt       time.Time `bson:"created_at" json:"created_at"`

	// Sharing origin tracking
	IsInherited     bool       `bson:"is_inherited" json:"is_inherited"`                               // Tracks whether access was granted directly or inherited from a parent
	InheritedFromID gocql.UUID `bson:"inherited_from_id,omitempty" json:"inherited_from_id,omitempty"` // InheritedFromID identifies which parent collection granted this access
}

type CreateCollectionService interface {
	Execute(ctx context.Context, req *CreateCollectionRequestDTO) (*CollectionResponseDTO, error)
}

type createCollectionServiceImpl struct {
	config             *config.Configuration
	logger             *zap.Logger
	userGetByIDUseCase uc_user.UserGetByIDUseCase
	repo               dom_collection.CollectionRepository
	tagRepo            tag.Repository
}

func NewCreateCollectionService(
	config *config.Configuration,
	logger *zap.Logger,
	userGetByIDUseCase uc_user.UserGetByIDUseCase,
	repo dom_collection.CollectionRepository,
	tagRepo tag.Repository,
) CreateCollectionService {
	logger = logger.Named("CreateCollectionService")
	return &createCollectionServiceImpl{
		config:             config,
		logger:             logger,
		userGetByIDUseCase: userGetByIDUseCase,
		repo:               repo,
		tagRepo:            tagRepo,
	}
}

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

	e := make(map[string]string)
	if req.ID.String() == "" {
		e["encrypted_name"] = "Client-side generated ID is required"
	}
	if req.EncryptedName == "" {
		e["encrypted_name"] = "Collection name is required"
	}
	if req.CollectionType == "" {
		e["collection_type"] = "Collection type is required"
	} else if req.CollectionType != dom_collection.CollectionTypeFolder && req.CollectionType != dom_collection.CollectionTypeAlbum {
		e["collection_type"] = "Collection type must be either 'folder' or 'album'"
	}
	// Check pointer and then content
	if req.EncryptedCollectionKey == nil || req.EncryptedCollectionKey.Ciphertext == nil || len(req.EncryptedCollectionKey.Ciphertext) == 0 {
		e["encrypted_collection_key"] = "Encrypted collection key ciphertext is required"
	}
	if req.EncryptedCollectionKey == nil || req.EncryptedCollectionKey.Nonce == nil || len(req.EncryptedCollectionKey.Nonce) == 0 {
		e["encrypted_collection_key"] = "Encrypted collection key nonce is required"
	}

	if len(e) != 0 {
		svc.logger.Warn("Failed validation",
			zap.Any("error", e))
		return nil, httperror.NewForBadRequest(&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.NewForInternalServerErrorWithSingleField("message", "Authentication context error")
	}

	federateduser, err := svc.userGetByIDUseCase.Execute(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("Failed getting user from database: %v", err)
	}
	if federateduser == nil {
		return nil, fmt.Errorf("User does not exist for user id: %v", userID.String())
	}

	//
	// STEP 3: Create collection object by mapping DTO and applying server-side logic
	//
	now := time.Now()

	// Map all fields from the request DTO to the domain object.
	// This copies client-provided values including potential ID, OwnerID, timestamps, etc.
	collection := mapCollectionDTOToDomain(req, userID, now)

	// Apply server-side mandatory fields/overrides for the top-level collection.
	// These values are managed by the backend regardless of what the client provides in the DTO.
	// This ensures data integrity and reflects the server's perspective of the creation event.
	collection.ID = gocql.TimeUUID()                        // Always generate a new ID on the server for a new creation
	collection.OwnerID = userID                             // The authenticated user is the authoritative owner
	collection.CreatedAt = now                              // Server timestamp for creation
	collection.ModifiedAt = now                             // Server timestamp for modification
	collection.CreatedByUserID = userID                     // The authenticated user is the creator
	collection.ModifiedByUserID = userID                    // The authenticated user is the initial modifier
	collection.Version = 1                                  // Collection creation **always** starts mutation version at 1.
	collection.State = dom_collection.CollectionStateActive // Collection creation **always** starts in active state.

	// Ensure owner membership exists with Admin permissions.
	// Check if the owner is already present in the members list copied from the DTO.
	ownerAlreadyMember := false
	for i := range collection.Members { // Iterate by index to allow modification if needed
		if collection.Members[i].RecipientID == userID {
			// Owner is found. Ensure they have Admin permission and correct granted_by/is_inherited status.
			collection.Members[i].RecipientEmail = federateduser.Email
			collection.Members[i].PermissionLevel = dom_collection.CollectionPermissionAdmin
			collection.Members[i].GrantedByID = userID
			collection.Members[i].IsInherited = false
			// NOTE: We intentionally do NOT set EncryptedCollectionKey here for the owner
			// The owner accesses the collection key through their master key, not through
			// the encrypted member key. This is validated in the repository layer.
			collection.Members[i].EncryptedCollectionKey = nil
			// Optionally update membership CreatedAt here if server should control it, otherwise keep DTO value.
			// collection.Members[i].CreatedAt = now
			ownerAlreadyMember = true
			svc.logger.Debug("✅ Owner membership updated with Admin permissions (no encrypted key needed)")
			break
		}
	}

	// If owner is not in the members list, add their mandatory membership.
	if !ownerAlreadyMember {
		svc.logger.Debug("☑️ Owner is not in the members list, add their mandatory membership now")
		ownerMembership := dom_collection.CollectionMembership{
			ID:              gocql.TimeUUID(), // Unique ID for this specific membership record
			RecipientID:     userID,
			RecipientEmail:  federateduser.Email,
			CollectionID:    collection.ID,                            // Link to the newly created collection ID
			PermissionLevel: dom_collection.CollectionPermissionAdmin, // Owner must have Admin
			GrantedByID:     userID,                                   // Owner implicitly grants themselves permission
			IsInherited:     false,                                    // Owner membership is never inherited
			CreatedAt:       now,                                      // Server timestamp for membership creation
			// NOTE: EncryptedCollectionKey is intentionally nil for owner memberships
			// The owner has access to the collection key through their master key
			// This is validated in the repository layer which allows nil encrypted keys for owners
			EncryptedCollectionKey: nil,
			// InheritedFromID is nil for direct membership.
		}
		// Append the mandatory owner membership. If req.Members was empty, this initializes the slice.
		collection.Members = append(collection.Members, ownerMembership)

		svc.logger.Debug("✅ Owner membership added with Admin permissions (no encrypted key needed)")
	}

	svc.logger.Debug("🔍 Collection debugging info",
		zap.String("collectionID", collection.ID.String()),
		zap.String("collectionOwnerID", collection.OwnerID.String()),
		zap.String("currentUserID", userID.String()),
		zap.Int("totalMembers", len(collection.Members)),
		zap.String("encryptedName", collection.EncryptedName))

	for i, memberDTO := range collection.Members {
		isOwner := memberDTO.RecipientID == collection.OwnerID
		svc.logger.Debug("🔍 Cloud collection member DTO",
			zap.Int("memberIndex", i),
			zap.String("memberID", memberDTO.ID.String()),
			zap.String("recipientID", memberDTO.RecipientID.String()),
			zap.String("recipientEmail", validation.MaskEmail(memberDTO.RecipientEmail)),
			zap.String("permissionLevel", memberDTO.PermissionLevel),
			zap.Bool("isInherited", memberDTO.IsInherited),
			zap.Bool("isOwner", isOwner),
			zap.Int("encryptedKeyLength", len(memberDTO.EncryptedCollectionKey)))
	}

	// ENHANCED DEBUGGING: Log current user info for comparison
	svc.logger.Debug("🔍 Current user info for comparison",
		zap.String("currentUserID", federateduser.ID.String()),
		zap.String("currentUserEmail", validation.MaskEmail(federateduser.Email)),
		zap.String("currentUserName", federateduser.Name))

	// Note: Fields like ParentID, AncestorIDs, EncryptedCollectionKey,
	// EncryptedName, CollectionType, and recursively mapped Children are copied directly from the DTO
	// by the mapCollectionDTOToDomain function before server overrides. This fulfills the
	// prompt's requirement to copy these fields from the DTO.

	//
	// STEP 3.5: Look up and embed tags if TagIDs were provided
	//
	if len(req.TagIDs) > 0 {
		svc.logger.Debug("🏷️ Looking up tags to embed in collection",
			zap.Int("tagCount", len(req.TagIDs)))

		var embeddedTags []tag.EmbeddedTag
		for _, tagID := range req.TagIDs {
			tagObj, err := svc.tagRepo.GetByID(ctx, tagID)
			if err != nil {
				svc.logger.Warn("Failed to get tag for embedding, skipping",
					zap.String("tagID", tagID.String()),
					zap.Error(err))
				continue
			}
			if tagObj == nil {
				svc.logger.Warn("Tag not found for embedding, skipping",
					zap.String("tagID", tagID.String()))
				continue
			}
			// Convert Tag to EmbeddedTag
			embedded := tagObj.ToEmbeddedTag()
			if embedded != nil {
				embeddedTags = append(embeddedTags, *embedded)
				svc.logger.Debug("🏷️ Tag embedded successfully",
					zap.String("tagID", tagID.String()))
			}
		}
		collection.Tags = embeddedTags
		svc.logger.Debug("🏷️ Tags embedded in collection",
			zap.Int("embeddedCount", len(embeddedTags)))
	}

	//
	// STEP 4: Create collection in repository
	//

	if err := svc.repo.Create(ctx, collection); err != nil {
		svc.logger.Error("Failed to create collection",
			zap.Any("error", err),
			zap.Any("owner_id", collection.OwnerID),
			zap.String("name", collection.EncryptedName))
		return nil, err
	}

	//
	// STEP 5: Map domain model to response DTO
	//
	// The mapCollectionToDTO helper is used here to convert the created domain object back
	// into the response DTO format, potentially excluding sensitive fields like keys
	// or specific membership details not meant for the general response.
	response := mapCollectionToDTO(collection, 0, federateduser.Email)

	svc.logger.Debug("Collection created successfully",
		zap.Any("collection_id", collection.ID),
		zap.Any("owner_id", collection.OwnerID))

	return response, nil
}
