// suggestions.go is the M5c per-user artist-suggestion service. Reads
// the user's likes + plays, projects them through artist_similarity_unmatched
// via a single CTE, returns top-N candidates with top-3 attribution seeds
// resolved to artist names.
package recommendation

import (
	"context"
	"fmt"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"

	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

// ArtistSuggestion is one ranked candidate with its top-3 attribution seeds.
type ArtistSuggestion struct {
	MBID        string
	Name        string
	Score       float64
	Attribution []SeedContribution
}

// SeedContribution is one of the top-3 contributing seeds for a candidate.
type SeedContribution struct {
	ArtistID     pgtype.UUID
	Name         string
	Contribution float64
	IsLiked      bool
	PlayCount    int64
}

// SuggestArtists returns top-N artist suggestions for the user. limit is
// capped at 50 (default 12 when out of range); halfLifeDays is the
// recency-decay half-life for plays (default 30, operator-tunable).
func SuggestArtists(ctx context.Context, pool *pgxpool.Pool, userID pgtype.UUID, halfLifeDays float64, limit int) ([]ArtistSuggestion, error) {
	if limit <= 0 || limit > 50 {
		limit = 12
	}
	if halfLifeDays <= 0 {
		halfLifeDays = 30
	}
	q := dbq.New(pool)
	rows, err := q.SuggestArtistsForUser(ctx, dbq.SuggestArtistsForUserParams{
		UserID:  userID,
		Column2: halfLifeDays,
		Limit:   int32(limit),
	})
	if err != nil {
		return nil, fmt.Errorf("suggest: query: %w", err)
	}
	if len(rows) == 0 {
		return []ArtistSuggestion{}, nil
	}

	// Collect the union of top-3 seed IDs across all rows for one batched
	// name lookup. pgtype.UUID is a comparable struct so it works as a map
	// key directly.
	seedSet := make(map[pgtype.UUID]struct{}, len(rows)*3)
	for _, r := range rows {
		for _, sid := range r.TopSeedIds {
			seedSet[sid] = struct{}{}
		}
	}
	seedIDs := make([]pgtype.UUID, 0, len(seedSet))
	for id := range seedSet {
		seedIDs = append(seedIDs, id)
	}
	artists, err := q.GetArtistsByIDs(ctx, seedIDs)
	if err != nil {
		return nil, fmt.Errorf("suggest: resolve seeds: %w", err)
	}
	nameByID := make(map[pgtype.UUID]string, len(artists))
	for _, a := range artists {
		nameByID[a.ID] = a.Name
	}

	out := make([]ArtistSuggestion, 0, len(rows))
	for _, r := range rows {
		attribution := make([]SeedContribution, 0, len(r.TopSeedIds))
		for i, sid := range r.TopSeedIds {
			if i >= len(r.TopContributions) || i >= len(r.TopIsLiked) || i >= len(r.TopPlayCounts) {
				break
			}
			attribution = append(attribution, SeedContribution{
				ArtistID:     sid,
				Name:         nameByID[sid],
				Contribution: r.TopContributions[i],
				IsLiked:      r.TopIsLiked[i],
				PlayCount:    r.TopPlayCounts[i],
			})
		}
		out = append(out, ArtistSuggestion{
			MBID:        r.CandidateMbid,
			Name:        r.CandidateName,
			Score:       r.TotalScore,
			Attribution: attribution,
		})
	}
	return out, nil
}
