package web

import (
	"context"
	"log/slog"
	"time"

	"pete/internal/games/trivia"
	"pete/internal/opentdb"
	"pete/internal/storage"
)

// Keeping the trivia bank stocked.
//
// The bank is not consumed by play — a question drawn is still there afterwards
// — so this loop is about *variety*, not supply. It fills each difficulty up to
// a target and then has nothing to do, which is why a pass that finds the bank
// full costs three COUNT queries and no network at all.

// bankTarget is how many questions of each difficulty we want to hold. Twelve
// rungs drawn from four hundred is enough that a regular player doesn't start
// recognising them, and it's a size OpenTDB's pool can actually fill.
const bankTarget = 400

// bankMaxFetches bounds one pass. At OpenTDB's politeness interval this is a
// couple of minutes of drip, after which the loop goes back to sleep rather than
// hammering a free API for an hour to top up the last few questions.
const bankMaxFetches = 12

// bankInterval is how often we go back and look. The bank is a slow-moving
// thing: it only grows, and it only needs to grow once.
const bankInterval = 12 * time.Hour

// StartTriviaBank launches the refill loop if the casino is on. Safe to call
// unconditionally; a no-op when games are off.
func (s *Server) StartTriviaBank(ctx context.Context) {
	if !s.gamesReady() {
		return
	}
	go s.runTriviaBank(ctx)
}

func (s *Server) runTriviaBank(ctx context.Context) {
	slog.Info("games: trivia bank refill started", "target", bankTarget, "interval", bankInterval)

	s.refillTriviaBank(ctx)

	ticker := time.NewTicker(bankInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			s.refillTriviaBank(ctx)
		}
	}
}

// refillTriviaBank tops each difficulty up toward the target, politely.
//
// Every failure here is survivable and none of them stop the loop: OpenTDB is a
// free API that is sometimes down, and a thin bank costs a player nothing worse
// than a "give it a minute" when they try to start a ladder.
func (s *Server) refillTriviaBank(ctx context.Context) {
	client := opentdb.New()
	fetches := 0

	for _, t := range trivia.Tiers {
		for fetches < bankMaxFetches {
			have, err := storage.CountTrivia(t.Difficulty)
			if err != nil {
				slog.Error("games: trivia bank count", "difficulty", t.Difficulty, "err", err)
				break
			}
			if have >= bankTarget {
				break
			}

			qs, err := client.Fetch(ctx, t.Difficulty, opentdb.Batch)
			fetches++
			if err != nil {
				if ctx.Err() != nil {
					return
				}
				slog.Warn("games: trivia bank fetch", "difficulty", t.Difficulty, "err", err)
				// Whatever went wrong, waiting is the only sensible response: the
				// likeliest cause is the rate limit, and retrying at once earns another.
				if !sleepCtx(ctx, opentdb.Politeness) {
					return
				}
				continue
			}

			added, err := storage.AddTriviaQuestions(t.Difficulty, qs)
			if err != nil {
				slog.Error("games: trivia bank store", "difficulty", t.Difficulty, "err", err)
				break
			}
			slog.Info("games: trivia bank filled",
				"difficulty", t.Difficulty, "fetched", len(qs), "new", added, "have", have+added)

			// The API hands back random batches, so once the bank is deep the
			// overlap gets heavy and a batch adds almost nothing new. When it adds
			// nothing at all, this difficulty has given us what it has: stop asking.
			if added == 0 {
				break
			}
			if !sleepCtx(ctx, opentdb.Politeness) {
				return
			}
		}
	}
}

// sleepCtx waits, unless we're being shut down. Reports false if we are.
func sleepCtx(ctx context.Context, d time.Duration) bool {
	t := time.NewTimer(d)
	defer t.Stop()
	select {
	case <-ctx.Done():
		return false
	case <-t.C:
		return true
	}
}
