package db

import (
	"context"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/redis/go-redis/v9"
)

// Rdb is the shared Redis connection, or nil when Redis is not configured.
//
// Deliberately the *same* instance the express backend uses. POS presence is
// read by the rider app, which talks to that backend, and a second Redis would
// mean either cross-service HTTP calls on every board refresh or two copies of
// the truth about which tills are alive.
//
// Key namespaces do not collide: express owns `delivery:*`, `city:*`,
// `rider_*`; POS owns `pos:*`. Worth keeping that way — a shared datastore only
// stays safe while each writer's keys are obviously its own.
var Rdb *redis.Client

// RedisCtx is the background context for Redis calls made outside a request.
var RedisCtx = context.Background()

// InitRedis connects if REDIS_HOST is set, and does nothing if it is not.
//
// Redis is optional here: without it the POS health board goes dark, but bills
// still arrive and commit. That is the right failure — losing presence is an
// inconvenience, losing a sale is not — so this never aborts startup.
func InitRedis() {
	host := strings.TrimSpace(os.Getenv("REDIS_HOST"))
	if host == "" {
		log.Println("redis: REDIS_HOST not set, POS presence disabled")
		return
	}

	port := getEnv("REDIS_PORT", "6379")
	dbIndex, err := strconv.Atoi(getEnv("REDIS_DB", "0"))
	if err != nil {
		dbIndex = 0
	}

	Rdb = redis.NewClient(&redis.Options{
		Addr:     host + ":" + port,
		Username: getEnv("REDIS_USER", "default"),
		Password: os.Getenv("REDIS_PASSWORD"),
		DB:       dbIndex,

		// Short on purpose. A degraded Redis must fail fast rather than tie up
		// a pooled connection for tens of seconds — the express backend learned
		// this the hard way, where a 10s x 3-retry config let one stuck call
		// hold a connection for ~35s and exhausted the pool under load.
		DialTimeout:  5 * time.Second,
		ReadTimeout:  3 * time.Second,
		WriteTimeout: 3 * time.Second,
		PoolTimeout:  4 * time.Second,
	})

	ctx, cancel := context.WithTimeout(RedisCtx, 5*time.Second)
	defer cancel()

	if err := Rdb.Ping(ctx).Err(); err != nil {
		// Logged, not fatal. A broker that cannot be reached is fatal because
		// bills would silently queue; Redis being down only costs the board.
		log.Printf("redis: could not reach %s:%s — POS presence will be unavailable: %v", host, port, err)
		Rdb = nil
		return
	}

	log.Printf("✅ Redis connected at %s:%s (db %d)", host, port, dbIndex)
}

// CloseRedis releases the pool on shutdown.
func CloseRedis() {
	if Rdb == nil {
		return
	}
	if err := Rdb.Close(); err != nil {
		log.Printf("redis: close failed: %v", err)
	}
}
