package intro

import (
	"context"
	"errors"
	"log"
	"sync"
	"time"

	"cloud.google.com/go/firestore"
)

const (
	bucketBaseURL = "https://storage.googleapis.com/reimage-assets"
	cacheTTL      = 5 * time.Minute
)

// Scene is the Firestore schema for one entry in the "intro" collection.
// Doc IDs are "{app}_{subject}_{preset}" — subject is the thing being
// transformed (a room for remodel, a yard for landscaping, etc.).
type Scene struct {
	VariationPaths []string `firestore:"variation_paths"`
}

// ErrNotFound is returned when the entry is missing or has no variations.
var ErrNotFound = errors.New("intro scene not found")

// Whole-collection cache. The endpoint is unauth'd, so per-doc caching would
// let attacker input amplify into Firestore reads. Cache size is content-bounded
// (team-curated), so no eviction is needed; traffic pins to ~1 read per cacheTTL.
var (
	cache     map[string]Scene
	cacheTime time.Time
	cacheMu   sync.RWMutex
)

// GetIntro returns the GCS URL for a pre-generated "after" image. Variation
// wraps modulo the available count, so any int is safe.
func GetIntro(client *firestore.Client, app, subject, preset string, variation int) (string, error) {
	scenes, err := loadScenes(client)
	if err != nil {
		return "", err
	}
	scene, ok := scenes[app+"_"+subject+"_"+preset]
	if !ok || len(scene.VariationPaths) == 0 {
		return "", ErrNotFound
	}
	n := len(scene.VariationPaths)
	idx := ((variation % n) + n) % n
	return bucketBaseURL + "/" + scene.VariationPaths[idx], nil
}

// loadScenes returns the cached collection, refreshing if stale. On transient
// Firestore error it falls back to the previous snapshot rather than 5xx-ing
// the endpoint — same stale-on-error pattern as catalog.go.
func loadScenes(client *firestore.Client) (map[string]Scene, error) {
	cacheMu.RLock()
	if cache != nil && time.Since(cacheTime) < cacheTTL {
		result := cache
		cacheMu.RUnlock()
		return result, nil
	}
	cacheMu.RUnlock()

	docs, err := client.Collection("intro").Documents(context.Background()).GetAll()
	if err != nil {
		cacheMu.RLock()
		stale := cache
		cacheMu.RUnlock()
		if stale != nil {
			log.Printf("Warning: Intro: Firestore error, serving stale cache: %v\n", err)
			return stale, nil
		}
		return nil, err
	}

	next := make(map[string]Scene, len(docs))
	for _, doc := range docs {
		var s Scene
		if err := doc.DataTo(&s); err != nil {
			log.Printf("Error: Intro: skip bad doc %s: %v\n", doc.Ref.ID, err)
			continue
		}
		next[doc.Ref.ID] = s
	}

	cacheMu.Lock()
	cache = next
	cacheTime = time.Now()
	cacheMu.Unlock()
	return next, nil
}
