package recommendation

import (
	"context"
	"encoding/json"
	"time"

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

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

// LoadCandidates fetches the candidate pool for radio scoring. Combines
// the existing track+stats query with a one-shot bulk fetch of the user's
// active contextual_likes, mapping each candidate to its max similarity
// against currentVector. Pass currentVector with Seed=true to short-circuit
// the contextual term to 0 (cold-start path).
func LoadCandidates(
	ctx context.Context,
	q *dbq.Queries,
	userID, seedID pgtype.UUID,
	recentlyPlayedHours int,
	currentVector SessionVector,
) ([]Candidate, error) {
	rows, err := q.LoadRadioCandidates(ctx, dbq.LoadRadioCandidatesParams{
		UserID:  userID,
		ID:      seedID,
		Column3: float64(recentlyPlayedHours),
	})
	if err != nil {
		return nil, err
	}

	likes, err := loadContextualLikesByTrack(ctx, q, userID)
	if err != nil {
		return nil, err
	}

	out := make([]Candidate, 0, len(rows))
	for _, r := range rows {
		var lpt *time.Time
		if r.LastPlayedAt.Valid {
			t := r.LastPlayedAt.Time
			lpt = &t
		}
		ctxScore := ContextualMatchScore(currentVector, likes[r.Track.ID], DefaultSimilarityWeights)
		out = append(out, Candidate{
			Track: r.Track,
			Inputs: ScoringInputs{
				IsGeneralLiked:       r.IsLiked,
				LastPlayedAt:         lpt,
				PlayCount:            int(r.PlayCount),
				SkipCount:            int(r.SkipCount),
				ContextualMatchScore: ctxScore,
			},
		})
	}
	return out, nil
}

// loadContextualLikesByTrack fetches the user's active contextual_likes in
// one query and groups them by track_id. Rows whose session_vector fails
// to unmarshal are skipped with no error (don't poison scoring over one
// bad row); the SQL query already filters NULL vectors.
func loadContextualLikesByTrack(
	ctx context.Context,
	q *dbq.Queries,
	userID pgtype.UUID,
) (map[pgtype.UUID][]SessionVector, error) {
	rows, err := q.ListActiveContextualLikesForUser(ctx, userID)
	if err != nil {
		return nil, err
	}
	out := make(map[pgtype.UUID][]SessionVector, len(rows))
	for _, r := range rows {
		var v SessionVector
		if err := json.Unmarshal(r.SessionVector, &v); err != nil {
			continue
		}
		out[r.TrackID] = append(out[r.TrackID], v)
	}
	return out, nil
}
