// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/tag/listcollectionsbytag.go
package tag

import (
	"context"
	"encoding/base64"
	"fmt"
	"sort"

	"github.com/gocql/gocql"

	dom_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
	dom_tag "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/tag"
)

type ListCollectionsByTagUseCase struct {
	tagRepo        dom_tag.Repository
	collectionRepo dom_collection.CollectionRepository
}

func NewListCollectionsByTagUseCase(
	tagRepo dom_tag.Repository,
	collectionRepo dom_collection.CollectionRepository,
) *ListCollectionsByTagUseCase {
	return &ListCollectionsByTagUseCase{
		tagRepo:        tagRepo,
		collectionRepo: collectionRepo,
	}
}

// Execute returns collections that have ALL specified tags (AND logic)
func (uc *ListCollectionsByTagUseCase) Execute(
	ctx context.Context,
	userID gocql.UUID,
	tagIDs []gocql.UUID,
	limit int,
	cursor string,
) ([]*dom_collection.Collection, string, error) {
	if len(tagIDs) == 0 {
		return []*dom_collection.Collection{}, "", nil
	}

	if limit <= 0 || limit > 100 {
		limit = 50
	}

	// Verify all tags exist and belong to user
	for _, tagID := range tagIDs {
		tag, err := uc.tagRepo.GetByID(ctx, tagID)
		if err != nil {
			return nil, "", fmt.Errorf("tag %s not found: %w", tagID.String(), err)
		}
		if tag.UserID != userID {
			return nil, "", fmt.Errorf("unauthorized: tag %s does not belong to user", tagID.String())
		}
	}

	// Query each tag and build collection ID -> count map
	collectionCounts := make(map[gocql.UUID]int)
	collectionData := make(map[gocql.UUID]*dom_collection.Collection)

	for _, tagID := range tagIDs {
		// Get collections for this tag
		collections, err := uc.collectionRepo.ListByTagID(ctx, tagID)
		if err != nil {
			return nil, "", fmt.Errorf("failed to list collections by tag: %w", err)
		}

		for _, collection := range collections {
			// Filter by user ownership
			if collection.OwnerID != userID {
				continue
			}

			collectionCounts[collection.ID]++

			// Store collection data on first occurrence
			if _, exists := collectionData[collection.ID]; !exists {
				collectionData[collection.ID] = collection
			}
		}
	}

	// Filter to collections that have ALL tags (AND logic)
	var intersectionCollections []*dom_collection.Collection
	for collectionID, count := range collectionCounts {
		if count == len(tagIDs) {
			intersectionCollections = append(intersectionCollections, collectionData[collectionID])
		}
	}

	// Sort by ID for consistent ordering
	sort.Slice(intersectionCollections, func(i, j int) bool {
		return intersectionCollections[i].ID.String() < intersectionCollections[j].ID.String()
	})

	// Apply cursor pagination
	var cursorCollectionID gocql.UUID
	if cursor != "" {
		decoded, err := base64.StdEncoding.DecodeString(cursor)
		if err == nil {
			cursorCollectionID, _ = gocql.ParseUUID(string(decoded))
		}
	}

	// Filter by cursor
	filteredCollections := make([]*dom_collection.Collection, 0, limit)
	foundCursor := cursorCollectionID.String() == "00000000-0000-0000-0000-000000000000"

	for _, collection := range intersectionCollections {
		if !foundCursor {
			if collection.ID.String() > cursorCollectionID.String() {
				foundCursor = true
			} else {
				continue
			}
		}
		filteredCollections = append(filteredCollections, collection)
		if len(filteredCollections) >= limit {
			break
		}
	}

	// Generate next cursor
	var nextCursor string
	if len(filteredCollections) == limit && len(intersectionCollections) > len(filteredCollections) {
		lastCollectionID := filteredCollections[len(filteredCollections)-1].ID
		nextCursor = base64.StdEncoding.EncodeToString([]byte(lastCollectionID.String()))
	}

	return filteredCollections, nextCursor, nil
}
