// Package playsessions implements the listening-session lifecycle from
// spec §6: a session is a contiguous sequence of plays by a user with no
// gap longer than the configured timeout (default 30 minutes) between
// events.
package playsessions

import (
	"context"
	"errors"
	"time"

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

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

// FindOrCreate returns the session id for the given user and event time.
// If the user's most recent session was last touched within `timeout`, that
// session is extended (last_event_at and track_count updated). Otherwise a
// new session is inserted.
//
// q is *dbq.Queries; pass either the pool-level Queries or one bound to a
// transaction so callers can compose this into a larger atomic operation.
func FindOrCreate(
	ctx context.Context,
	q *dbq.Queries,
	userID pgtype.UUID,
	eventTime time.Time,
	clientID string,
	timeout time.Duration,
) (pgtype.UUID, error) {
	prev, err := q.GetMostRecentPlaySessionForUser(ctx, userID)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return pgtype.UUID{}, err
	}
	if err == nil {
		// "Within window" includes the exact-boundary case: gap <= timeout extends.
		gap := eventTime.Sub(prev.LastEventAt.Time)
		if gap <= timeout {
			if err := q.TouchPlaySessionLastEvent(ctx, dbq.TouchPlaySessionLastEventParams{
				ID:          prev.ID,
				LastEventAt: pgtype.Timestamptz{Time: eventTime, Valid: true},
			}); err != nil {
				return pgtype.UUID{}, err
			}
			return prev.ID, nil
		}
	}
	// Either no prior session, or the gap exceeded the timeout.
	var clientIDPtr *string
	if clientID != "" {
		clientIDPtr = &clientID
	}
	row, err := q.InsertPlaySession(ctx, dbq.InsertPlaySessionParams{
		UserID:    userID,
		StartedAt: pgtype.Timestamptz{Time: eventTime, Valid: true},
		ClientID:  clientIDPtr,
	})
	if err != nil {
		return pgtype.UUID{}, err
	}
	// Bump track_count to 1 on creation so the contract "every FindOrCreate is
	// the start of one play" stays true (insert leaves track_count default 0).
	if err := q.TouchPlaySessionLastEvent(ctx, dbq.TouchPlaySessionLastEventParams{
		ID:          row.ID,
		LastEventAt: pgtype.Timestamptz{Time: eventTime, Valid: true},
	}); err != nil {
		return pgtype.UUID{}, err
	}
	return row.ID, nil
}
