package db

import (
	"context"
	"fmt"
	"log"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

// catalogueImagesPrefix is where product photos for the brand catalogue
// live in the bucket: daily/brands/{brand}/{image_id}/image_NNN.<ext>
const catalogueImagesPrefix = "daily/brands/"

// imageStore caches a public-URL listing of catalogue product images so
// GET requests never have to call out to S3 themselves.
type imageStore struct {
	mu        sync.RWMutex
	images    map[string]map[string][]string // brand -> image_id -> sorted public URLs
	client    *s3.Client
	bucket    string
	publicURL string // base URL objects are served from, e.g. https://nearle.sgp1.digitaloceanspaces.com
}

var ImageStore *imageStore

// connectImageStore wires up the DigitalOcean Spaces (S3-compatible) client
// used to resolve catalogue product images. Like the catalogue DB, this must
// never block or fail app startup — if S3 env vars are absent, image URLs
// are simply omitted from catalogue responses.
func connectImageStore() {
	if getEnv("USE_S3", "") != "true" {
		fmt.Println("⚠️  S3 not enabled, skipping image store")
		return
	}

	endpoint := getEnv("S3_ENDPOINT", "")
	bucket := getEnv("S3_BUCKET", "")
	accessKey := getEnv("S3_ACCESS_KEY", "")
	secretKey := getEnv("S3_SECRET_KEY", "")
	region := getEnv("S3_REGION", "")
	if endpoint == "" || bucket == "" || accessKey == "" || secretKey == "" {
		fmt.Println("⚠️  S3 env vars incomplete, skipping image store")
		return
	}

	// S3_ENDPOINT is bucket-qualified (e.g. https://nearle.sgp1.digitaloceanspaces.com).
	// The SDK's virtual-hosted-style client re-prepends the bucket to whatever
	// host it's given, so the client must be pointed at the bare region host
	// instead, or listing requests end up addressed to "nearle.nearle...".
	regionHost := strings.TrimPrefix(endpoint, "https://")
	regionHost = strings.TrimPrefix(regionHost, "http://")
	regionHost = strings.TrimPrefix(regionHost, bucket+".")

	cfg, err := awsconfig.LoadDefaultConfig(context.Background(),
		awsconfig.WithRegion(region),
		awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
	)
	if err != nil {
		log.Println("❌ Could not configure S3 client:", err)
		return
	}

	client := s3.NewFromConfig(cfg, func(o *s3.Options) {
		o.BaseEndpoint = aws.String("https://" + regionHost)
	})

	ImageStore = &imageStore{
		images:    make(map[string]map[string][]string),
		client:    client,
		bucket:    bucket,
		publicURL: strings.TrimSuffix(endpoint, "/"),
	}

	if err := ImageStore.refresh(); err != nil {
		log.Println("❌ Initial catalogue image listing failed:", err)
	} else {
		fmt.Println("✅ Catalogue image store loaded")
	}

	go ImageStore.refreshLoop()
}

func (s *imageStore) refreshLoop() {
	ticker := time.NewTicker(30 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		if err := s.refresh(); err != nil {
			log.Println("⚠️  Catalogue image refresh failed:", err)
		}
	}
}

// refresh lists every object under daily/brands/ and rebuilds the
// brand -> image_id -> URLs map from scratch, then swaps it in atomically.
func (s *imageStore) refresh() error {
	next := make(map[string]map[string][]string)

	paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
		Bucket: aws.String(s.bucket),
		Prefix: aws.String(catalogueImagesPrefix),
	})

	for paginator.HasMorePages() {
		page, err := paginator.NextPage(context.Background())
		if err != nil {
			return fmt.Errorf("listing %s: %w", catalogueImagesPrefix, err)
		}
		for _, obj := range page.Contents {
			key := aws.ToString(obj.Key)
			// daily/brands/{brand}/{image_id}/image_NNN.ext
			parts := strings.SplitN(strings.TrimPrefix(key, catalogueImagesPrefix), "/", 3)
			if len(parts) != 3 || parts[2] == "" {
				continue
			}
			brand := strings.ToLower(parts[0])
			imageID := parts[1]

			if next[brand] == nil {
				next[brand] = make(map[string][]string)
			}
			next[brand][imageID] = append(next[brand][imageID], s.publicURL+"/"+key)
		}
	}

	for _, byImage := range next {
		for _, urls := range byImage {
			sort.Strings(urls)
		}
	}

	s.mu.Lock()
	s.images = next
	s.mu.Unlock()

	return nil
}

// GetImages returns the cached public image URLs for a brand + image_id.
// Safe to call even if the image store was never initialized.
func GetImages(brand, imageID string) []string {
	if ImageStore == nil {
		return nil
	}
	ImageStore.mu.RLock()
	defer ImageStore.mu.RUnlock()
	return ImageStore.images[strings.ToLower(brand)][imageID]
}
