// monorepo/cloud/maplefile-backend/internal/maplefile/repo/collection/share.go
package collection

import (
	"context"
	"fmt"
	"time"

	"github.com/gocql/gocql"
	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
	"go.uber.org/zap"
)

func (impl *collectionRepositoryImpl) AddMember(ctx context.Context, collectionID gocql.UUID, membership *dom_collection.CollectionMembership) error {
	if membership == nil {
		return fmt.Errorf("membership cannot be nil")
	}

	impl.Logger.Info("starting add member process",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.String("recipient_email", validation.MaskEmail(membership.RecipientEmail)),
		zap.String("permission_level", membership.PermissionLevel))

	// Validate membership data with enhanced checks
	if !impl.isValidUUID(membership.RecipientID) {
		return fmt.Errorf("invalid recipient ID")
	}
	if membership.RecipientEmail == "" {
		return fmt.Errorf("recipient email is required")
	}
	if membership.PermissionLevel == "" {
		membership.PermissionLevel = dom_collection.CollectionPermissionReadOnly
	}

	// CRITICAL: Validate encrypted collection key for shared members
	if len(membership.EncryptedCollectionKey) == 0 {
		impl.Logger.Error("CRITICAL: Attempt to add member without encrypted collection key",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.String("recipient_email", validation.MaskEmail(membership.RecipientEmail)),
			zap.Int("encrypted_key_length", len(membership.EncryptedCollectionKey)))
		return fmt.Errorf("encrypted collection key is required for shared members")
	}

	// Additional validation: ensure the encrypted key is reasonable size
	if len(membership.EncryptedCollectionKey) < 32 {
		impl.Logger.Error("encrypted collection key appears too short",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.Int("encrypted_key_length", len(membership.EncryptedCollectionKey)))
		return fmt.Errorf("encrypted collection key appears invalid (got %d bytes, expected at least 32)", len(membership.EncryptedCollectionKey))
	}

	impl.Logger.Info("validated encrypted collection key for new member",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.Int("encrypted_key_length", len(membership.EncryptedCollectionKey)))

	// Load collection
	collection, err := impl.Get(ctx, collectionID)
	if err != nil {
		impl.Logger.Error("failed to get collection for member addition",
			zap.String("collection_id", collectionID.String()),
			zap.Error(err))
		return fmt.Errorf("failed to get collection: %w", err)
	}

	if collection == nil {
		return fmt.Errorf("collection not found")
	}

	impl.Logger.Info("loaded collection for member addition",
		zap.String("collection_id", collection.ID.String()),
		zap.String("collection_state", collection.State),
		zap.Int("existing_members", len(collection.Members)))

	// Ensure member has an ID BEFORE adding to collection
	if !impl.isValidUUID(membership.ID) {
		membership.ID = gocql.TimeUUID()
		impl.Logger.Debug("generated new member ID", zap.String("member_id", membership.ID.String()))
	}

	// Set creation time if not set
	if membership.CreatedAt.IsZero() {
		membership.CreatedAt = time.Now()
	}

	// Set collection ID (ensure it matches)
	membership.CollectionID = collectionID

	// Check if member already exists and update or add
	memberExists := false
	for i, existingMember := range collection.Members {
		if existingMember.RecipientID == membership.RecipientID {
			impl.Logger.Info("updating existing collection member",
				zap.String("collection_id", collectionID.String()),
				zap.String("recipient_id", membership.RecipientID.String()),
				zap.String("old_permission", existingMember.PermissionLevel),
				zap.String("new_permission", membership.PermissionLevel))

			// IMPORTANT: Preserve the existing member ID to avoid creating a new one
			membership.ID = existingMember.ID
			collection.Members[i] = *membership
			memberExists = true
			break
		}
	}

	if !memberExists {
		impl.Logger.Info("adding new collection member",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.String("permission_level", membership.PermissionLevel))

		collection.Members = append(collection.Members, *membership)

		impl.Logger.Info("DEBUGGING: Member added to collection.Members slice",
			zap.String("collection_id", collectionID.String()),
			zap.String("new_member_id", membership.ID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.Int("total_members_now", len(collection.Members)))
	}

	// Update version
	collection.Version++
	collection.ModifiedAt = time.Now()

	impl.Logger.Info("prepared collection for update with member",
		zap.String("collection_id", collection.ID.String()),
		zap.Int("total_members", len(collection.Members)),
		zap.Uint64("version", collection.Version))

	// DEBUGGING: Log all members that will be sent to Update method
	impl.Logger.Info("DEBUGGING: About to call Update() with these members:")
	for debugIdx, debugMember := range collection.Members {
		isOwner := debugMember.RecipientID == collection.OwnerID
		impl.Logger.Info("DEBUGGING: Member in collection.Members slice",
			zap.Int("index", debugIdx),
			zap.String("member_id", debugMember.ID.String()),
			zap.String("recipient_id", debugMember.RecipientID.String()),
			zap.String("recipient_email", validation.MaskEmail(debugMember.RecipientEmail)),
			zap.String("permission_level", debugMember.PermissionLevel),
			zap.Bool("is_owner", isOwner),
			zap.Int("encrypted_key_length", len(debugMember.EncryptedCollectionKey)))
	}

	// Log all members for debugging
	for i, member := range collection.Members {
		isOwner := member.RecipientID == collection.OwnerID
		impl.Logger.Debug("collection member details",
			zap.Int("member_index", i),
			zap.String("member_id", member.ID.String()),
			zap.String("recipient_id", member.RecipientID.String()),
			zap.String("recipient_email", validation.MaskEmail(member.RecipientEmail)),
			zap.String("permission_level", member.PermissionLevel),
			zap.Bool("is_inherited", member.IsInherited),
			zap.Bool("is_owner", isOwner),
			zap.Int("encrypted_key_length", len(member.EncryptedCollectionKey)))
	}

	// Call update - the Update method itself is atomic and reliable
	err = impl.Update(ctx, collection)
	if err != nil {
		impl.Logger.Error("failed to update collection with new member",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.Error(err))
		return fmt.Errorf("failed to update collection: %w", err)
	}

	impl.Logger.Info("successfully added member to collection",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.String("member_id", membership.ID.String()))

	// DEVELOPER NOTE:
	// Remove the immediate verification after update since Cassandra needs time to propagate:

	// // DEBUGGING: Test if we can query the members table directly
	// impl.Logger.Info("DEBUGGING: Testing direct access to members table")
	// err = impl.testMembersTableAccess(ctx, collectionID)
	// if err != nil {
	// 	impl.Logger.Error("DEBUGGING: Failed to access members table",
	// 		zap.String("collection_id", collectionID.String()),
	// 		zap.Error(err))
	// } else {
	// 	impl.Logger.Info("DEBUGGING: Members table access test successful",
	// 		zap.String("collection_id", collectionID.String()))
	// }

	return nil
}

// testDirectMemberInsert tests inserting directly into the members table (for debugging)
func (impl *collectionRepositoryImpl) testDirectMemberInsert(ctx context.Context, collectionID gocql.UUID, membership *dom_collection.CollectionMembership) error {
	impl.Logger.Info("DEBUGGING: Testing direct insert into members table",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()))

	query := `INSERT INTO collection_members_by_collection_id_and_recipient_id
		(collection_id, recipient_id, member_id, recipient_email, granted_by_id,
		 encrypted_collection_key, permission_level, created_at,
		 is_inherited, inherited_from_id)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`

	err := impl.Session.Query(query,
		collectionID, membership.RecipientID, membership.ID, membership.RecipientEmail,
		membership.GrantedByID, membership.EncryptedCollectionKey,
		membership.PermissionLevel, membership.CreatedAt,
		membership.IsInherited, membership.InheritedFromID).WithContext(ctx).Exec()

	if err != nil {
		impl.Logger.Error("DEBUGGING: Direct insert failed",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.Error(err))
		return fmt.Errorf("direct insert failed: %w", err)
	}

	impl.Logger.Info("DEBUGGING: Direct insert successful",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()))

	// Verify the insert worked
	var foundMemberID gocql.UUID
	verifyQuery := `SELECT member_id FROM collection_members_by_collection_id_and_recipient_id
		WHERE collection_id = ? AND recipient_id = ?`

	err = impl.Session.Query(verifyQuery, collectionID, membership.RecipientID).WithContext(ctx).Scan(&foundMemberID)
	if err != nil {
		if err == gocql.ErrNotFound {
			impl.Logger.Error("DEBUGGING: Direct insert verification failed - member not found",
				zap.String("collection_id", collectionID.String()),
				zap.String("recipient_id", membership.RecipientID.String()))
			return fmt.Errorf("direct insert verification failed - member not found")
		}
		impl.Logger.Error("DEBUGGING: Direct insert verification error",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_id", membership.RecipientID.String()),
			zap.Error(err))
		return fmt.Errorf("verification query failed: %w", err)
	}

	impl.Logger.Info("DEBUGGING: Direct insert verification successful",
		zap.String("collection_id", collectionID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.String("found_member_id", foundMemberID.String()))

	return nil
}

// testMembersTableAccess verifies we can read from the members table
func (impl *collectionRepositoryImpl) testMembersTableAccess(ctx context.Context, collectionID gocql.UUID) error {
	query := `SELECT COUNT(*) FROM collection_members_by_collection_id_and_recipient_id WHERE collection_id = ?`

	var count int
	err := impl.Session.Query(query, collectionID).WithContext(ctx).Scan(&count)
	if err != nil {
		return fmt.Errorf("failed to query members table: %w", err)
	}

	impl.Logger.Info("DEBUGGING: Members table query successful",
		zap.String("collection_id", collectionID.String()),
		zap.Int("member_count", count))

	return nil
}

func (impl *collectionRepositoryImpl) RemoveMember(ctx context.Context, collectionID, recipientID gocql.UUID) error {
	// Load collection, remove member, and save
	collection, err := impl.Get(ctx, collectionID)
	if err != nil {
		return fmt.Errorf("failed to get collection: %w", err)
	}

	if collection == nil {
		return fmt.Errorf("collection not found")
	}

	// Remove member from collection
	var updatedMembers []dom_collection.CollectionMembership
	found := false

	for _, member := range collection.Members {
		if member.RecipientID != recipientID {
			updatedMembers = append(updatedMembers, member)
		} else {
			found = true
		}
	}

	if !found {
		return fmt.Errorf("member not found in collection")
	}

	collection.Members = updatedMembers
	collection.Version++

	return impl.Update(ctx, collection)
}

func (impl *collectionRepositoryImpl) UpdateMemberPermission(ctx context.Context, collectionID, recipientID gocql.UUID, newPermission string) error {
	// Load collection, update member permission, and save
	collection, err := impl.Get(ctx, collectionID)
	if err != nil {
		return fmt.Errorf("failed to get collection: %w", err)
	}

	if collection == nil {
		return fmt.Errorf("collection not found")
	}

	// Update member permission
	found := false
	for i, member := range collection.Members {
		if member.RecipientID == recipientID {
			collection.Members[i].PermissionLevel = newPermission
			found = true
			break
		}
	}

	if !found {
		return fmt.Errorf("member not found in collection")
	}

	collection.Version++
	return impl.Update(ctx, collection)
}

func (impl *collectionRepositoryImpl) GetCollectionMembership(ctx context.Context, collectionID, recipientID gocql.UUID) (*dom_collection.CollectionMembership, error) {
	var membership dom_collection.CollectionMembership

	query := `SELECT recipient_id, member_id, recipient_email, granted_by_id,
		encrypted_collection_key, permission_level, created_at,
		is_inherited, inherited_from_id
		FROM collection_members_by_collection_id_and_recipient_id
		WHERE collection_id = ? AND recipient_id = ?`

	err := impl.Session.Query(query, collectionID, recipientID).WithContext(ctx).Scan(
		&membership.RecipientID, &membership.ID, &membership.RecipientEmail, &membership.GrantedByID,
		&membership.EncryptedCollectionKey, &membership.PermissionLevel,
		&membership.CreatedAt, &membership.IsInherited, &membership.InheritedFromID)

	if err != nil {
		if err == gocql.ErrNotFound {
			return nil, nil
		}
		return nil, err
	}

	membership.CollectionID = collectionID

	return &membership, nil
}

func (impl *collectionRepositoryImpl) AddMemberToHierarchy(ctx context.Context, rootID gocql.UUID, membership *dom_collection.CollectionMembership) error {
	// Get all descendants of the root collection
	descendants, err := impl.FindDescendants(ctx, rootID)
	if err != nil {
		return fmt.Errorf("failed to find descendants: %w", err)
	}

	impl.Logger.Info("adding member to collection hierarchy",
		zap.String("root_collection_id", rootID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.Int("descendants_count", len(descendants)))

	// Add to root collection
	if err := impl.AddMember(ctx, rootID, membership); err != nil {
		return fmt.Errorf("failed to add member to root collection: %w", err)
	}

	// Add to all descendants with inherited flag
	inheritedMembership := *membership
	inheritedMembership.IsInherited = true
	inheritedMembership.InheritedFromID = rootID

	successCount := 0
	for _, descendant := range descendants {
		// Generate new ID for each inherited membership
		inheritedMembership.ID = gocql.TimeUUID()

		if err := impl.AddMember(ctx, descendant.ID, &inheritedMembership); err != nil {
			impl.Logger.Warn("failed to add inherited member to descendant",
				zap.String("descendant_id", descendant.ID.String()),
				zap.String("recipient_id", membership.RecipientID.String()),
				zap.Error(err))
		} else {
			successCount++
		}
	}

	impl.Logger.Info("completed hierarchy member addition",
		zap.String("root_collection_id", rootID.String()),
		zap.String("recipient_id", membership.RecipientID.String()),
		zap.Int("total_descendants", len(descendants)),
		zap.Int("successful_additions", successCount))

	return nil
}

func (impl *collectionRepositoryImpl) RemoveMemberFromHierarchy(ctx context.Context, rootID, recipientID gocql.UUID) error {
	// Get all descendants of the root collection
	descendants, err := impl.FindDescendants(ctx, rootID)
	if err != nil {
		return fmt.Errorf("failed to find descendants: %w", err)
	}

	// Remove from root collection
	if err := impl.RemoveMember(ctx, rootID, recipientID); err != nil {
		return fmt.Errorf("failed to remove member from root collection: %w", err)
	}

	// Remove from all descendants where access was inherited from this root
	for _, descendant := range descendants {
		// Only remove if the membership was inherited from this root
		membership, err := impl.GetCollectionMembership(ctx, descendant.ID, recipientID)
		if err != nil {
			impl.Logger.Warn("failed to get membership for descendant",
				zap.String("descendant_id", descendant.ID.String()),
				zap.Error(err))
			continue
		}

		if membership != nil && membership.IsInherited && membership.InheritedFromID == rootID {
			if err := impl.RemoveMember(ctx, descendant.ID, recipientID); err != nil {
				impl.Logger.Warn("failed to remove inherited member from descendant",
					zap.String("descendant_id", descendant.ID.String()),
					zap.String("recipient_id", recipientID.String()),
					zap.Error(err))
			}
		}
	}

	return nil
}

// RemoveUserFromAllCollections removes a user from all collections they are a member of
// Used for GDPR right-to-be-forgotten implementation
// Returns a list of collection IDs that were modified
func (impl *collectionRepositoryImpl) RemoveUserFromAllCollections(ctx context.Context, userID gocql.UUID, userEmail string) ([]gocql.UUID, error) {
	impl.Logger.Info("Removing user from all shared collections",
		zap.String("user_id", userID.String()),
		zap.String("user_email", validation.MaskEmail(userEmail)))

	// Get all collections shared with the user
	sharedCollections, err := impl.GetCollectionsSharedWithUser(ctx, userID)
	if err != nil {
		impl.Logger.Error("Failed to get collections shared with user",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, fmt.Errorf("failed to get shared collections: %w", err)
	}

	impl.Logger.Info("Found shared collections for user",
		zap.String("user_id", userID.String()),
		zap.Int("collection_count", len(sharedCollections)))

	var modifiedCollections []gocql.UUID
	successCount := 0
	failureCount := 0

	// Remove user from each collection
	for _, collection := range sharedCollections {
		err := impl.RemoveMember(ctx, collection.ID, userID)
		if err != nil {
			impl.Logger.Warn("Failed to remove user from collection",
				zap.String("collection_id", collection.ID.String()),
				zap.String("user_id", userID.String()),
				zap.Error(err))
			failureCount++
			// Continue with other collections despite error
			continue
		}

		modifiedCollections = append(modifiedCollections, collection.ID)
		successCount++

		impl.Logger.Debug("Removed user from collection",
			zap.String("collection_id", collection.ID.String()),
			zap.String("user_id", userID.String()))
	}

	impl.Logger.Info("✅ Completed removing user from shared collections",
		zap.String("user_id", userID.String()),
		zap.Int("total_collections", len(sharedCollections)),
		zap.Int("success_count", successCount),
		zap.Int("failure_count", failureCount),
		zap.Int("modified_collections", len(modifiedCollections)))

	// Return success even if some removals failed - partial success is acceptable
	return modifiedCollections, nil
}
