package main

import (
	"context"
	"errors"
	"fmt"
	"github.com/allegro/bigcache/v3"
	"github.com/dgraph-io/ristretto/v2"
	"github.com/maypok86/otter/v2"
	"github.com/redis/go-redis/v9"
	"github.com/redis/rueidis"
	"os"
	"time"
)

func remote() error {
	port := os.Getenv("REDIS_PORT")
	if port == "" {
		return errors.New("REDIS_PORT must point to the temporary fixture")
	}
	addr := "127.0.0.1:" + port
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	r := redis.NewClient(&redis.Options{Addr: addr, MaxRetries: -1, DialTimeout: time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, ContextTimeoutEnabled: true, PoolSize: 4})
	defer r.Close()
	if err := r.Set(ctx, "lab08:tools:g", "value", time.Minute).Err(); err != nil {
		return err
	}
	v, err := r.Get(ctx, "lab08:tools:g").Result()
	if err != nil {
		return err
	}
	_, missing := r.Get(ctx, "lab08:tools:missing").Result()
	if v != "value" || !errors.Is(missing, redis.Nil) {
		return errors.New("go-redis result mismatch")
	}
	fmt.Printf("go-redis: value=%s missing=%v\n", v, errors.Is(missing, redis.Nil))
	u, err := rueidis.NewClient(rueidis.ClientOption{InitAddress: []string{addr}, DisableRetry: true, CacheSizeEachConn: 1 << 20})
	if err != nil {
		return err
	}
	defer u.Close()
	if err = u.Do(ctx, u.B().Set().Key("lab08:tools:r").Value("value").Ex(time.Minute).Build()).Error(); err != nil {
		return err
	}
	first := u.DoCache(ctx, u.B().Get().Key("lab08:tools:r").Cache(), time.Minute)
	v, err = first.ToString()
	if err != nil {
		return err
	}
	second := u.DoCache(ctx, u.B().Get().Key("lab08:tools:r").Cache(), time.Minute)
	if _, err = second.ToString(); err != nil {
		return err
	}
	if v != "value" || !second.IsCacheHit() {
		return errors.New("rueidis cache mismatch")
	}
	fmt.Printf("rueidis: value=%s second-hit=%v\n", v, second.IsCacheHit())
	return nil
}
func local() error {
	r, err := ristretto.NewCache(&ristretto.Config[string, string]{NumCounters: 1000, MaxCost: 1024, BufferItems: 64})
	if err != nil {
		return err
	}
	defer r.Close()
	queued := r.Set("oversize", "value", 10000)
	r.Wait()
	_, hit := r.Get("oversize")
	if !queued || hit {
		return errors.New("ristretto admission mismatch")
	}
	fmt.Printf("ristretto: queued=%v retained=%v\n", queued, hit)
	cfg := bigcache.DefaultConfig(time.Second)
	cfg.Shards = 1
	cfg.CleanWindow = 0
	cfg.MaxEntriesInWindow = 16
	cfg.MaxEntrySize = 64
	cfg.HardMaxCacheSize = 1
	cfg.Verbose = false
	b, err := bigcache.New(context.Background(), cfg)
	if err != nil {
		return err
	}
	defer b.Close()
	if err = b.Set("k", []byte("value")); err != nil {
		return err
	}
	time.Sleep(2100 * time.Millisecond) // Clock has seconds resolution; no cleanup writes follow.
	bv, info, err := b.GetWithInfo("k")
	if err != nil {
		return err
	}
	if string(bv) != "value" || info.EntryStatus != bigcache.Expired {
		return errors.New("bigcache expiry mismatch")
	}
	fmt.Printf("bigcache: value=%s expired=%v\n", bv, info.EntryStatus == bigcache.Expired)
	o, err := otter.New(&otter.Options[string, string]{MaximumSize: 10, ExpiryCalculator: otter.ExpiryWriting[string, string](time.Minute)})
	if err != nil {
		return err
	}
	defer o.StopAllGoroutines()
	loads := 0
	loader := otter.LoaderFunc[string, string](func(context.Context, string) (string, error) { loads++; return "value", nil })
	ctx := context.Background()
	ov, err := o.Get(ctx, "k", loader)
	if err != nil {
		return err
	}
	if _, err = o.Get(ctx, "k", loader); err != nil {
		return err
	}
	if ov != "value" || loads != 1 {
		return errors.New("otter loader mismatch")
	}
	fmt.Printf("otter: value=%s loads=%d\n", ov, loads)
	return nil
}
func main() {
	if err := remote(); err != nil {
		panic(err)
	}
	if err := local(); err != nil {
		panic(err)
	}
}
