package store

import (
	"context"
	"errors"
	"fmt"
	"time"

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

// validTranscriptStatuses bounds SetTranscriptStatus input. "" clears the status
// (column NULL); the three named states mirror migration 007's documented values.
var validTranscriptStatuses = map[string]bool{
	"":             true,
	"none":         true,
	"rate_limited": true,
	"fetched":      true,
}

// SetTranscriptStatus records the outcome of the last transcript attempt for a
// video (migration 007). When status is "rate_limited" it also stamps
// rate_limited_at = NOW() so the runner can back off; every other status clears
// that timestamp. "" unsets the status (column NULL). An unknown status is
// rejected. Scoped via withUser, so RLS confines the UPDATE to the caller's own
// video; ErrNotFound when the user has no such video.
func (s *Store) SetTranscriptStatus(ctx context.Context, userID, videoID, status string) error {
	if !validTranscriptStatuses[status] {
		return fmt.Errorf("store: invalid transcript status %q", status)
	}
	return s.withUser(ctx, userID, func(tx pgx.Tx) error {
		ct, err := tx.Exec(ctx,
			`UPDATE videos
			    SET transcript_status = NULLIF($1, ''),
			        rate_limited_at = CASE WHEN $1 = 'rate_limited' THEN NOW() ELSE NULL END
			  WHERE id = $2`,
			status, videoID)
		if err != nil {
			return fmt.Errorf("store: set transcript status: %w", err)
		}
		if ct.RowsAffected() == 0 {
			return ErrNotFound
		}
		return nil
	})
}

// GetTranscriptStatus returns a video's transcript_status ("" when unset/NULL).
// Returns ErrNotFound when the user has no such video. Scoped via withUser.
func (s *Store) GetTranscriptStatus(ctx context.Context, userID, videoID string) (string, error) {
	var status string
	if err := s.withUser(ctx, userID, func(tx pgx.Tx) error {
		err := tx.QueryRow(ctx,
			`SELECT COALESCE(transcript_status, '') FROM videos WHERE id = $1`, videoID).Scan(&status)
		if errors.Is(err, pgx.ErrNoRows) {
			return ErrNotFound
		}
		return err
	}); err != nil {
		if errors.Is(err, ErrNotFound) {
			return "", ErrNotFound
		}
		return "", fmt.Errorf("store: get transcript status: %w", err)
	}
	return status, nil
}

// RateLimitedVideoIDs returns the user's videos currently in the "rate_limited"
// state, mapped to when the 429 was stamped (rate_limited_at). The run loop loads
// it once per pass (mirroring SeenVideoIDs) to skip re-fetching a video still
// inside the backoff window, saving caption requests. Scoped by user_id.
func (s *Store) RateLimitedVideoIDs(ctx context.Context, userID string) (map[string]time.Time, error) {
	out := make(map[string]time.Time)
	if err := s.withUser(ctx, userID, func(tx pgx.Tx) error {
		rows, err := tx.Query(ctx,
			`SELECT id, rate_limited_at FROM videos
			  WHERE user_id = $1 AND transcript_status = 'rate_limited' AND rate_limited_at IS NOT NULL`,
			userID)
		if err != nil {
			return fmt.Errorf("store: rate limited video ids: %w", err)
		}
		defer rows.Close()

		for rows.Next() {
			var (
				id string
				at time.Time
			)
			if err := rows.Scan(&id, &at); err != nil {
				return fmt.Errorf("store: scan rate limited id: %w", err)
			}
			out[id] = at
		}
		if err := rows.Err(); err != nil {
			return fmt.Errorf("store: iterate rate limited ids: %w", err)
		}
		return nil
	}); err != nil {
		return nil, err
	}
	return out, nil
}
