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

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

	"github.com/gocql/gocql"

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

type ListFilesByTagUseCase struct {
	tagRepo  dom_tag.Repository
	fileRepo dom_file.FileMetadataRepository
}

func NewListFilesByTagUseCase(
	tagRepo dom_tag.Repository,
	fileRepo dom_file.FileMetadataRepository,
) *ListFilesByTagUseCase {
	return &ListFilesByTagUseCase{
		tagRepo:  tagRepo,
		fileRepo: fileRepo,
	}
}

// Execute returns files that have ALL specified tags (AND logic)
func (uc *ListFilesByTagUseCase) Execute(
	ctx context.Context,
	userID gocql.UUID,
	tagIDs []gocql.UUID,
	limit int,
	cursor string,
) ([]*dom_file.File, string, error) {
	if len(tagIDs) == 0 {
		return []*dom_file.File{}, "", 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 file ID -> count map
	fileCounts := make(map[gocql.UUID]int)
	fileData := make(map[gocql.UUID]*dom_file.File)

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

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

			fileCounts[file.ID]++

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

	// Filter to files that have ALL tags (AND logic)
	var intersectionFiles []*dom_file.File
	for fileID, count := range fileCounts {
		if count == len(tagIDs) {
			intersectionFiles = append(intersectionFiles, fileData[fileID])
		}
	}

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

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

	// Filter by cursor
	filteredFiles := make([]*dom_file.File, 0, limit)
	foundCursor := cursorFileID.String() == "00000000-0000-0000-0000-000000000000"

	for _, file := range intersectionFiles {
		if !foundCursor {
			if file.ID.String() > cursorFileID.String() {
				foundCursor = true
			} else {
				continue
			}
		}
		filteredFiles = append(filteredFiles, file)
		if len(filteredFiles) >= limit {
			break
		}
	}

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

	return filteredFiles, nextCursor, nil
}
