// monorepo/cloud/maplefile-backend/internal/maplefile/repo/collection/get.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"
)

// Core helper methods for loading collections with members
func (impl *collectionRepositoryImpl) loadCollectionWithMembers(ctx context.Context, collectionID gocql.UUID) (*dom_collection.Collection, error) {
	// 1. Load base collection
	collection, err := impl.getBaseCollection(ctx, collectionID)
	if err != nil || collection == nil {
		return collection, err
	}

	// 2. Load and populate members
	members, err := impl.getCollectionMembers(ctx, collectionID)
	if err != nil {
		return nil, err
	}
	collection.Members = members

	return collection, nil
}

func (impl *collectionRepositoryImpl) getBaseCollection(ctx context.Context, id gocql.UUID) (*dom_collection.Collection, error) {
	var (
		encryptedName, collectionType, encryptedKeyJSON string
		encryptedCustomIcon                             string
		ancestorIDsJSON                                 string
		tagsJSON                                        string
		parentID, ownerID, createdByUserID, modifiedByUserID gocql.UUID
		createdAt, modifiedAt, tombstoneExpiry               time.Time
		version, tombstoneVersion                            uint64
		state                                                string
		fileCount                                            int64
	)

	query := `SELECT id, owner_id, encrypted_name, collection_type, encrypted_collection_key,
		encrypted_custom_icon, parent_id, ancestor_ids, file_count, tags, created_at, created_by_user_id, modified_at,
		modified_by_user_id, version, state, tombstone_version, tombstone_expiry
		FROM collections_by_id WHERE id = ?`

	err := impl.Session.Query(query, id).WithContext(ctx).Scan(
		&id, &ownerID, &encryptedName, &collectionType, &encryptedKeyJSON,
		&encryptedCustomIcon, &parentID, &ancestorIDsJSON, &fileCount, &tagsJSON, &createdAt, &createdByUserID,
		&modifiedAt, &modifiedByUserID, &version, &state, &tombstoneVersion, &tombstoneExpiry)

	if err != nil {
		if err == gocql.ErrNotFound {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get collection: %w", err)
	}

	// Deserialize complex fields
	ancestorIDs, err := impl.deserializeAncestorIDs(ancestorIDsJSON)
	if err != nil {
		return nil, fmt.Errorf("failed to deserialize ancestor IDs: %w", err)
	}

	encryptedKey, err := impl.deserializeEncryptedCollectionKey(encryptedKeyJSON)
	if err != nil {
		return nil, fmt.Errorf("failed to deserialize encrypted collection key: %w", err)
	}

	tags, err := impl.deserializeTags(tagsJSON)
	if err != nil {
		return nil, fmt.Errorf("failed to deserialize tags: %w", err)
	}

	collection := &dom_collection.Collection{
		ID:                     id,
		OwnerID:                ownerID,
		EncryptedName:          encryptedName,
		CollectionType:         collectionType,
		EncryptedCollectionKey: encryptedKey,
		EncryptedCustomIcon:    encryptedCustomIcon,
		Members:                []dom_collection.CollectionMembership{}, // Will be populated separately
		ParentID:               parentID,
		AncestorIDs:            ancestorIDs,
		FileCount:              fileCount,
		Tags:                   tags,
		CreatedAt:              createdAt,
		CreatedByUserID:        createdByUserID,
		ModifiedAt:             modifiedAt,
		ModifiedByUserID:       modifiedByUserID,
		Version:                version,
		State:                  state,
		TombstoneVersion:       tombstoneVersion,
		TombstoneExpiry:        tombstoneExpiry,
	}

	return collection, nil
}

func (impl *collectionRepositoryImpl) getCollectionMembers(ctx context.Context, collectionID gocql.UUID) ([]dom_collection.CollectionMembership, error) {
	var members []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 = ?`

	impl.Logger.Info("🔍 GET MEMBERS: Querying collection members",
		zap.String("collection_id", collectionID.String()),
		zap.String("query", query))

	iter := impl.Session.Query(query, collectionID).WithContext(ctx).Iter()

	var (
		recipientID, memberID, grantedByID, inheritedFromID gocql.UUID
		recipientEmail, permissionLevel                     string
		encryptedCollectionKey                              []byte
		createdAt                                           time.Time
		isInherited                                         bool
	)

	for iter.Scan(&recipientID, &memberID, &recipientEmail, &grantedByID,
		&encryptedCollectionKey, &permissionLevel, &createdAt,
		&isInherited, &inheritedFromID) {

		impl.Logger.Info("🔍 GET MEMBERS: Found member",
			zap.String("collection_id", collectionID.String()),
			zap.String("recipient_email", validation.MaskEmail(recipientEmail)),
			zap.String("recipient_id", recipientID.String()),
			zap.Int("encrypted_key_length", len(encryptedCollectionKey)),
			zap.String("permission_level", permissionLevel))

		member := dom_collection.CollectionMembership{
			ID:                     memberID,
			CollectionID:           collectionID,
			RecipientID:            recipientID,
			RecipientEmail:         recipientEmail,
			GrantedByID:            grantedByID,
			EncryptedCollectionKey: encryptedCollectionKey,
			PermissionLevel:        permissionLevel,
			CreatedAt:              createdAt,
			IsInherited:            isInherited,
			InheritedFromID:        inheritedFromID,
		}
		members = append(members, member)
	}

	if err := iter.Close(); err != nil {
		impl.Logger.Error("🔍 GET MEMBERS: Failed to iterate members",
			zap.String("collection_id", collectionID.String()),
			zap.Error(err))
		return nil, err
	}

	impl.Logger.Info("🔍 GET MEMBERS: Query completed",
		zap.String("collection_id", collectionID.String()),
		zap.Int("members_found", len(members)))

	return members, nil
}

func (impl *collectionRepositoryImpl) loadMultipleCollectionsWithMembers(ctx context.Context, collectionIDs []gocql.UUID) ([]*dom_collection.Collection, error) {
	if len(collectionIDs) == 0 {
		return []*dom_collection.Collection{}, nil
	}

	var collections []*dom_collection.Collection
	for _, id := range collectionIDs {
		collection, err := impl.loadCollectionWithMembers(ctx, id)
		if err != nil {
			impl.Logger.Warn("failed to load collection",
				zap.String("collection_id", id.String()),
				zap.Error(err))
			continue
		}
		if collection != nil {
			collections = append(collections, collection)
		}
	}

	return collections, nil
}

func (impl *collectionRepositoryImpl) Get(ctx context.Context, id gocql.UUID) (*dom_collection.Collection, error) {
	return impl.loadCollectionWithMembers(ctx, id)
}

// FIXED: Removed state filtering from query, filter in memory instead
func (impl *collectionRepositoryImpl) GetAllByUserID(ctx context.Context, ownerID gocql.UUID) ([]*dom_collection.Collection, error) {
	var collectionIDs []gocql.UUID

	query := `SELECT collection_id FROM collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
		WHERE user_id = ? AND access_type = 'owner'`

	iter := impl.Session.Query(query, ownerID).WithContext(ctx).Iter()

	var collectionID gocql.UUID
	for iter.Scan(&collectionID) {
		collectionIDs = append(collectionIDs, collectionID)
	}

	if err := iter.Close(); err != nil {
		impl.Logger.Error("failed to get collections",
			zap.Any("user_id", ownerID),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to get collections by owner: %w", err)
	}

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, collectionIDs)
	if err != nil {
		return nil, err
	}

	// Filter to only active collections
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		if collection.State == dom_collection.CollectionStateActive {
			activeCollections = append(activeCollections, collection)
		}
	}

	impl.Logger.Debug("retrieved owned collections efficiently",
		zap.String("owner_id", ownerID.String()),
		zap.Int("total_found", len(allCollections)),
		zap.Int("active_count", len(activeCollections)))

	return activeCollections, nil
}

// FIXED: Removed state filtering from query, filter in memory instead
func (impl *collectionRepositoryImpl) GetCollectionsSharedWithUser(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, error) {
	impl.Logger.Info("🔍 REPO: Getting collections shared with user",
		zap.String("user_id", userID.String()))

	var collectionIDs []gocql.UUID

	query := `SELECT collection_id FROM collections_by_user_id_and_access_type_with_desc_modified_at_and_asc_collection_id
		WHERE user_id = ? AND access_type = 'member'`

	impl.Logger.Info("🔍 REPO: Executing query",
		zap.String("user_id", userID.String()),
		zap.String("query", query))

	iter := impl.Session.Query(query, userID).WithContext(ctx).Iter()

	var collectionID gocql.UUID
	for iter.Scan(&collectionID) {
		collectionIDs = append(collectionIDs, collectionID)
		impl.Logger.Info("🔍 REPO: Found collection ID in index",
			zap.String("collection_id", collectionID.String()))
	}

	if err := iter.Close(); err != nil {
		impl.Logger.Error("🔍 REPO: Query iteration failed",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, fmt.Errorf("failed to get shared collections: %w", err)
	}

	impl.Logger.Info("🔍 REPO: Query completed",
		zap.String("user_id", userID.String()),
		zap.Int("collection_ids_found", len(collectionIDs)))

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, collectionIDs)
	if err != nil {
		impl.Logger.Error("🔍 REPO: Failed to load collections",
			zap.String("user_id", userID.String()),
			zap.Error(err))
		return nil, err
	}

	impl.Logger.Info("🔍 REPO: Loaded collections",
		zap.String("user_id", userID.String()),
		zap.Int("collections_loaded", len(allCollections)))

	// Filter to only active collections AND collections where the user has actual membership
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		impl.Logger.Info("🔍 REPO: Checking collection state",
			zap.String("collection_id", collection.ID.String()),
			zap.String("state", collection.State),
			zap.Bool("is_active", collection.State == dom_collection.CollectionStateActive))

		if collection.State != dom_collection.CollectionStateActive {
			impl.Logger.Info("🔍 REPO: Skipping inactive collection",
				zap.String("collection_id", collection.ID.String()),
				zap.String("state", collection.State))
			continue
		}

		// Check if the user has actual membership in this collection
		// For GetCollectionsSharedWithUser, we MUST have a membership record
		// This is the source of truth, not the index
		hasMembership := false
		for _, member := range collection.Members {
			if member.RecipientID == userID {
				hasMembership = true
				impl.Logger.Info("🔍 REPO: User has membership in collection",
					zap.String("collection_id", collection.ID.String()),
					zap.String("user_id", userID.String()),
					zap.String("recipient_email", validation.MaskEmail(member.RecipientEmail)),
					zap.String("permission_level", member.PermissionLevel))
				break
			}
		}

		if !hasMembership {
			// No actual membership record found - this is stale index data
			// Skip this collection regardless of ownership
			impl.Logger.Warn("🔍 REPO: Skipping collection with no actual membership (stale index)",
				zap.String("collection_id", collection.ID.String()),
				zap.String("user_id", userID.String()),
				zap.Bool("is_owner", collection.OwnerID == userID),
				zap.Int("members_count", len(collection.Members)))
			continue
		}

		activeCollections = append(activeCollections, collection)
	}

	impl.Logger.Debug("retrieved shared collections efficiently",
		zap.String("user_id", userID.String()),
		zap.Int("total_found", len(allCollections)),
		zap.Int("active_count", len(activeCollections)))

	return activeCollections, nil
}

// NEW METHOD: Demonstrates querying across all access types when needed
func (impl *collectionRepositoryImpl) GetAllUserCollections(ctx context.Context, userID gocql.UUID) ([]*dom_collection.Collection, error) {
	var collectionIDs []gocql.UUID

	query := `SELECT collection_id FROM collections_by_user_id_with_desc_modified_at_and_asc_collection_id
		WHERE user_id = ?`

	iter := impl.Session.Query(query, userID).WithContext(ctx).Iter()

	var collectionID gocql.UUID
	for iter.Scan(&collectionID) {
		collectionIDs = append(collectionIDs, collectionID)
	}

	if err := iter.Close(); err != nil {
		return nil, fmt.Errorf("failed to get all user collections: %w", err)
	}

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, collectionIDs)
	if err != nil {
		return nil, err
	}

	// Filter to only active collections
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		if collection.State == dom_collection.CollectionStateActive {
			activeCollections = append(activeCollections, collection)
		}
	}

	impl.Logger.Debug("retrieved all user collections efficiently",
		zap.String("user_id", userID.String()),
		zap.Int("total_found", len(allCollections)),
		zap.Int("active_count", len(activeCollections)))

	return activeCollections, nil
}

// Uses composite partition key table for better performance
func (impl *collectionRepositoryImpl) FindByParent(ctx context.Context, parentID gocql.UUID) ([]*dom_collection.Collection, error) {
	var collectionIDs []gocql.UUID

	query := `SELECT collection_id FROM collections_by_parent_id_with_asc_created_at_and_asc_collection_id
		WHERE parent_id = ?`

	iter := impl.Session.Query(query, parentID).WithContext(ctx).Iter()

	var collectionID gocql.UUID
	for iter.Scan(&collectionID) {
		collectionIDs = append(collectionIDs, collectionID)
	}

	if err := iter.Close(); err != nil {
		return nil, fmt.Errorf("failed to find collections by parent: %w", err)
	}

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, collectionIDs)
	if err != nil {
		return nil, err
	}

	// Filter to only active collections
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		if collection.State == dom_collection.CollectionStateActive {
			activeCollections = append(activeCollections, collection)
		}
	}

	return activeCollections, nil
}

// Uses composite partition key for optimal performance
func (impl *collectionRepositoryImpl) FindRootCollections(ctx context.Context, ownerID gocql.UUID) ([]*dom_collection.Collection, error) {
	var collectionIDs []gocql.UUID

	// Use the composite partition key table for root collections
	nullParentID := impl.nullParentUUID()

	query := `SELECT collection_id FROM collections_by_parent_and_owner_id_with_asc_created_at_and_asc_collection_id
		WHERE parent_id = ? AND owner_id = ?`

	iter := impl.Session.Query(query, nullParentID, ownerID).WithContext(ctx).Iter()

	var collectionID gocql.UUID
	for iter.Scan(&collectionID) {
		collectionIDs = append(collectionIDs, collectionID)
	}

	if err := iter.Close(); err != nil {
		return nil, fmt.Errorf("failed to find root collections: %w", err)
	}

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, collectionIDs)
	if err != nil {
		return nil, err
	}

	// Filter to only active collections
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		if collection.State == dom_collection.CollectionStateActive {
			activeCollections = append(activeCollections, collection)
		}
	}

	return activeCollections, nil
}

// No more recursive queries - single efficient query
func (impl *collectionRepositoryImpl) FindDescendants(ctx context.Context, collectionID gocql.UUID) ([]*dom_collection.Collection, error) {
	var descendantIDs []gocql.UUID

	query := `SELECT collection_id FROM collections_by_ancestor_id_with_asc_depth_and_asc_collection_id
		WHERE ancestor_id = ?`

	iter := impl.Session.Query(query, collectionID).WithContext(ctx).Iter()

	var descendantID gocql.UUID
	for iter.Scan(&descendantID) {
		descendantIDs = append(descendantIDs, descendantID)
	}

	if err := iter.Close(); err != nil {
		return nil, fmt.Errorf("failed to find descendants: %w", err)
	}

	// Load collections and filter by state in memory
	allCollections, err := impl.loadMultipleCollectionsWithMembers(ctx, descendantIDs)
	if err != nil {
		return nil, err
	}

	// Filter to only active collections
	var activeCollections []*dom_collection.Collection
	for _, collection := range allCollections {
		if collection.State == dom_collection.CollectionStateActive {
			activeCollections = append(activeCollections, collection)
		}
	}

	return activeCollections, nil
}
