// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.31.1
// source: scrobble.sql

package dbq

import (
	"context"

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

const enqueueScrobble = `-- name: EnqueueScrobble :execrows
INSERT INTO scrobble_queue (user_id, play_event_id, status)
VALUES ($1, $2, 'pending')
ON CONFLICT (play_event_id) DO NOTHING
`

type EnqueueScrobbleParams struct {
	UserID      pgtype.UUID
	PlayEventID pgtype.UUID
}

// Idempotent: if a row exists for this play_event_id, ON CONFLICT does nothing
// and the affected-rows count is 0. Caller (MaybeEnqueue) doesn't treat 0 as
// an error.
func (q *Queries) EnqueueScrobble(ctx context.Context, arg EnqueueScrobbleParams) (int64, error) {
	result, err := q.db.Exec(ctx, enqueueScrobble, arg.UserID, arg.PlayEventID)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected(), nil
}

const listPendingScrobbles = `-- name: ListPendingScrobbles :many
SELECT
    sq.id           AS queue_id,
    sq.user_id      AS user_id,
    sq.play_event_id AS play_event_id,
    sq.attempts     AS attempts,
    u.listenbrainz_token AS lb_token,
    u.listenbrainz_enabled AS lb_enabled,
    pe.started_at   AS started_at,
    t.title         AS track_title,
    t.duration_ms   AS track_duration_ms,
    t.mbid          AS track_mbid,
    al.title        AS album_title,
    al.mbid         AS album_mbid,
    ar.name         AS artist_name,
    ar.mbid         AS artist_mbid
FROM scrobble_queue sq
JOIN users u       ON u.id  = sq.user_id
JOIN play_events pe ON pe.id = sq.play_event_id
JOIN tracks t      ON t.id  = pe.track_id
JOIN albums al     ON al.id = t.album_id
JOIN artists ar    ON ar.id = t.artist_id
WHERE sq.status = 'pending'
  AND sq.next_attempt_at <= now()
ORDER BY sq.next_attempt_at
LIMIT $1
`

type ListPendingScrobblesRow struct {
	QueueID         pgtype.UUID
	UserID          pgtype.UUID
	PlayEventID     pgtype.UUID
	Attempts        int32
	LbToken         *string
	LbEnabled       bool
	StartedAt       pgtype.Timestamptz
	TrackTitle      string
	TrackDurationMs int32
	TrackMbid       *string
	AlbumTitle      string
	AlbumMbid       *string
	ArtistName      string
	ArtistMbid      *string
}

// Worker pulls up to N pending rows whose next_attempt_at has passed.
// Joins play_events + tracks/albums/artists + users so the worker can
// assemble the LB Listen payload in one round-trip.
func (q *Queries) ListPendingScrobbles(ctx context.Context, limit int32) ([]ListPendingScrobblesRow, error) {
	rows, err := q.db.Query(ctx, listPendingScrobbles, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListPendingScrobblesRow
	for rows.Next() {
		var i ListPendingScrobblesRow
		if err := rows.Scan(
			&i.QueueID,
			&i.UserID,
			&i.PlayEventID,
			&i.Attempts,
			&i.LbToken,
			&i.LbEnabled,
			&i.StartedAt,
			&i.TrackTitle,
			&i.TrackDurationMs,
			&i.TrackMbid,
			&i.AlbumTitle,
			&i.AlbumMbid,
			&i.ArtistName,
			&i.ArtistMbid,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const markScrobbleFailed = `-- name: MarkScrobbleFailed :exec
UPDATE scrobble_queue
SET status     = 'failed',
    last_error = $2
WHERE id = $1
`

type MarkScrobbleFailedParams struct {
	ID        pgtype.UUID
	LastError *string
}

// After a permanent failure (4xx, exhausted retries, auth) — mark failed
// and keep the row for diagnostics.
func (q *Queries) MarkScrobbleFailed(ctx context.Context, arg MarkScrobbleFailedParams) error {
	_, err := q.db.Exec(ctx, markScrobbleFailed, arg.ID, arg.LastError)
	return err
}

const markScrobbleRetryAfter = `-- name: MarkScrobbleRetryAfter :exec
UPDATE scrobble_queue
SET next_attempt_at = $2
WHERE id = $1
`

type MarkScrobbleRetryAfterParams struct {
	ID            pgtype.UUID
	NextAttemptAt pgtype.Timestamptz
}

// After a 429: schedule next attempt per LB's Retry-After header, but do NOT
// increment attempts (the server told us to wait, not that we failed).
func (q *Queries) MarkScrobbleRetryAfter(ctx context.Context, arg MarkScrobbleRetryAfterParams) error {
	_, err := q.db.Exec(ctx, markScrobbleRetryAfter, arg.ID, arg.NextAttemptAt)
	return err
}

const markScrobbleSent = `-- name: MarkScrobbleSent :exec
WITH del AS (
    DELETE FROM scrobble_queue
    WHERE scrobble_queue.id = $1
    RETURNING play_event_id
)
UPDATE play_events
SET scrobbled_at = now()
WHERE play_events.id = (SELECT play_event_id FROM del)
`

// Successful submit. Delete the queue row AND stamp play_events.scrobbled_at
// in one statement via a CTE so partial failure is impossible.
func (q *Queries) MarkScrobbleSent(ctx context.Context, id pgtype.UUID) error {
	_, err := q.db.Exec(ctx, markScrobbleSent, id)
	return err
}

const rescheduleScrobble = `-- name: RescheduleScrobble :exec
UPDATE scrobble_queue
SET attempts        = attempts + 1,
    next_attempt_at = $2,
    last_error      = $3
WHERE id = $1
`

type RescheduleScrobbleParams struct {
	ID            pgtype.UUID
	NextAttemptAt pgtype.Timestamptz
	LastError     *string
}

// After a transient failure: increment attempts, schedule next attempt,
// record the error.
func (q *Queries) RescheduleScrobble(ctx context.Context, arg RescheduleScrobbleParams) error {
	_, err := q.db.Exec(ctx, rescheduleScrobble, arg.ID, arg.NextAttemptAt, arg.LastError)
	return err
}
